Delay two Pbind

ok now it works, the irregularities were coming from the sustain of the midi source, thanks

Glad to see it’s working.

I had already written this so I’m going to post it anyway.

If the code is not doing what you think it should be doing, then put in some debug or trace calls to get more visibility. (Also, here I’ve reformatted the code to make it friendlier to read.)

(
~a = nil;

w = Pbind(
	\midinote, Pseq([74, 76, 75], inf),
	\dur, Pseq([1, 3, Prand([3, 5, 7], 1).collect { |x| ~a = x }], inf).trace(prefix: "w dur: ")
);

x = Pbind(
	\midinote, Pseq([[67, 70, 62], [72, 67, 60]], inf),
	\dur, Pseq([4, Pfuncn({ ~a.debug("x retrieved") }, 1)], inf).trace(prefix: "x dur: ")
);

~run = Ppar([w, x]).play;
)

w dur: 1
x dur: 4
w dur: 3
x retrieved: nil  << HERE
w dur: 5

So… you’re expecting that if w and x are scheduled for the same time, then w will always wake up first, and populate ~a to be ready for x. But when the number of items is different, the debugging information shows this is not guaranteed.

Ptpar is the solution for this.

(
~a = nil;

w = Pbind(
	\midinote, Pseq([74, 76, 75], inf),
	\dur, Pseq([1, 3, Prand([3, 5, 7], 1).collect { |x| ~a = x }], inf).trace(prefix: "w dur: "),
	\timingOffset, 0.01
);

x = Pbind(
	\midinote, Pseq([[67, 70, 62], [72, 67, 60]], inf),
	\dur, Pseq([4, Pfuncn({ ~a.debug("x retrieved") }, 1)], inf).trace(prefix: "x dur: ")
);

~run = Ptpar([0, w, 0.01, x]).play;
)

w dur: 1
x dur: 4
w dur: 3
w dur: 7
x retrieved: 7  << 'w' did its bit first so now we are fine
x dur: 7

hjh

Just as a side-note, as you know with MIDI you don’t have the precision related to time-stamped messaging. However in the context of VSTPlugins Christof is thinking about implementing an analogue mechanism.

Last thing, I know to generate time ramped values on the server side (line, sweep, LFDNoise etc…) but how I do that on the language side? As an instance I would like to ramp the midi bend.
1 From 16383 to 100 in 10 sec
2 From 16383 to 100 in 10 sec, then from 100 to 8000 in 5 sec, then from 8000 to 15000 in 2 sec and so on
Thanks

(
//chord
x=Pbind(
\type, \midi,
	\midicmd,\noteOn,\midiout, m,\chan,0,\midinote, Pseq([[67,68,72,73]],inf),\dur,10).play;
//pitchbend for the chord
y=Pbind(
\type, \midi,
	\midicmd,\bend,\midiout, m,\chan,0,\dur,10,\val,//from 16383 to 100 in 10 sec
 ).play;

Ppar([x,y]).play(TempoClock(0.7));
)

I’ve already answered your originally 3rd question in my first post of this thread, you can use Pseg:

see also

I’ve also written a tutorial on this topic proposing a number of alternatives, see “Event patterns and LFOs” from the miSCellaneous_lib quark.

yes yes yes, sorry and thank you, so much information :slight_smile:

No worrys, pattern-land is huge :slight_smile:

To play a chord transposition sequence ?
This chord

 \midinote, Pseq([[ 74,73,69]],inf)  

In order to have:

\midinote, Pseq([ thischord*transposed one key down, thischord*transposed half a key up, and so on],inf);

Thanks

You can add patterns to patterns as well as to arrays:

Pbind(
	\midinote, [74, 73, 69] + Pseq([-2, 1], inf),
	\dur, 0.2
).play


Pbind(
	\midinote, Pstutter(3, Pseq((60..65), inf)) + [0, 7, 14] + Pseq([0, -3, 3], inf),
	\dur, 0.2
).play
1 Like

Pstutter(3, Pseq((60..65), inf)) + [0, 7, 14] + Pseq([0, -3, 3], inf),

Or, I’m pretty sure this is equivalent:

(Pseq((60..65), inf) +.x Pseq([0, -3, 3], inf)) + [0, 7, 14],

hjh

Not exactly, the cross adverb has to be applied to finite streams, to get the same we can e.g. do

(
Pbind(
	\midinote, Pn(Pseq((60..65)) +.x Pseq([0, -3, 3])) + [0, 7, 14],
	\dur, 0.2
).play
)

I omitted this in order to avoid another layer of mysteriousness, but it’s nice indeed.

1 Like

Actually, one steam can be infinite, but it should be left-hand one for the expected results.

(0..2) +.x [0, 10] // -> [ 0, 10, 1, 11, 2, 12 ]

(Pseries(0, 1) +.x Pseq([0, 10])).asStream.nextN(6)
 // -> [ 0, 10, 1, 11, 2, 12 ]

(Pseq([0, 10]) +.x Pseries(0, 1)).asStream.nextN(6) 
// -> [ 0, 1, 2, 3, 4, 5 ]

The even more confusing behavior is when you apply it to a combo of an array and an infinite stream

(Pseries(0, 1) +.x [0, 10]).asStream.nextN(3)
 // -> [ [ 0, 10 ], [ 0, 10 ], [ 0, 10 ] ]

([0, 10] +.x Pseries(0, 1)).asStream.nextN(3)
 // -> [ [ 0, 10 ], [ 1, 11 ], [ 2, 12 ] ]

You can [P]flatten the latter for the desired result…

([0, 10] +.x Pseries(0, 1)).flatten.asStream.nextN(6)
// -> [ 0, 10, 1, 11, 2, 12 ]

but there’s the confusing usability issue that a finite Pseq needs to go “on the right”, but a (finite, obvious) array needs to “go on the left” . Also, in the latter case, since you have to re-flatten, it’s no different than using a plain ‘+’

([0, 10] + Pseries(0, 1)).flatten.asStream.nextN(6)
// -> [ 0, 10, 1, 11, 2, 12 ]

There’s a special implementation for cross adverbs in BinaryOpXStream: when the right-hand side stream yields nil, it gets reset. (Cross adverbs are detected by Pbinop.asStream which uses BinaryOpXStream for the latter instead of BinaryOpStream.) That implementation makes it a non-commutative operation, but then so is its finite counterpart on arrays… BinaryOpXStream also works more like a flatMap in Java etc. As “proof”:

(0..2) +.x [0, 10] // same result as next line
(0..2).collect({|a| a +.x [0, 10]}).flatten // -> [ 0, 10, 1, 11, 2, 12 ]

// so, "patternizing" that idea
Pflatten(1, Pcollect({|a| a +.x [0, 10]}, Pseries(0, 1))).asStream.nextN(6) // or 
Pcollect({|a| a +.x [0, 10]}, Pseries(0, 1)).flatten.asStream.nextN(6) // or
Pseries(0, 1).collect({|a| a +.x [0, 10]}).flatten.asStream.nextN(6)
// -> [ 0, 10, 1, 11, 2, 12 ]

Because of the wrapping behavior of adding a single scalar to an array, that’s the same as

(0..2).collect({|a| a + [0, 10]}).flatten //-> [ 0, 10, 1, 11, 2, 12 ]; also
Pflatten(1, Pcollect({|a| a + [0, 10]}, Pseries(0, 1))).asStream.nextN(6) // etc.

But in the more general case where Pseries returned an array, there’s a difference where you flatten, or equivalently commuting the arguments of +.x (on an array), i.e row vs column “major order”, e.g.

[0, 10] +.x [0, 0.1] // -> [ 0, 0.1, 10, 10.1 ]
[0, 0.1] +.x [0, 10] // -> [ 0, 10, 0.1, 10.1 ]

(Pseries([0, 0.1], 1) +.x Pseq([0, 10])).flatten.asStream.nextN(12) // same as
Pcollect({|a| [0, 10] +.x a}, Pseries([0, 0.1], 1)).flatten.asStream.nextN(12)
// -> [ 0, 0.1, 10, 10.1, 1, 1.1, 11, 11.1, 2, 2.1, 12, 12.1 ]

(Pseries([0, 0.1], 1).flatten +.x Pseq([0, 10])).asStream.nextN(12) // same as
Pcollect({|a| a +.x [0, 10]}, Pseries([0, 0.1], 1)).flatten.asStream.nextN(12)
// -> [ 0, 10, 0.1, 10.1, 1, 11, 1.1, 11.1, 2, 12, 2.1, 12.1 ]

N.B., *.x is mostly called an “outer” rather than “cross” product/operation is most non-SC contexts. Actually, “Kronecker product” is probably more appropriate since the result of .x is flattened. The .t adverb in SC gives the true (unflattened) outer product.

It doesn’t give the expected results in this case, compare:
(I omit the chord addition as it’s irrelevant here)

// wanting to reduce this with adverbs:

(Pstutter(3, Pseq((60..65), inf)) + Pseq([0, -3, 3], inf)).iter.nextN(20)

-> [ 60, 57, 63, 61, 58, 64, 62, 59, 65, 63, 60, 66, 64, 61, 67, 65, 62, 68, 60, 57 ]


// both of these don't give the same

(Pseq((60..65), inf) +.x Pseq([0, -3, 3], inf)).iter.nextN(20)

-> [ 60, 57, 63, 60, 57, 63, 60, 57, 63, 60, 57, 63, 60, 57, 63, 60, 57, 63, 60, 57 ]

(Pseq((60..65)) +.x Pseq([0, -3, 3], inf)).iter.nextN(20)

-> [ 60, 57, 63, 60, 57, 63, 60, 57, 63, 60, 57, 63, 60, 57, 63, 60, 57, 63, 60, 57 ]



// That's why I suggested this

Pn(Pseq((60..65)) +.x Pseq([0, -3, 3])).iter.nextN(20)

-> [ 60, 57, 63, 61, 58, 64, 62, 59, 65, 63, 60, 66, 64, 61, 67, 65, 62, 68, 60, 57 ]

But I’m sure there are alternatives :slight_smile:

Well yes, e.g. that would be one:

([0, -3, 3] +.x Pseq((60..65), inf)).flatten.iter.nextN(20)

.x is a no-op in that one; the following does the same

([0, -3, 3] + Pseq((60..65), inf)).flatten.iter.nextN(20)

The .x version needs a finite Pseq on the right.

(Pseq((60..65), inf) +.x Pseq([0, -3, 3])).iter.nextN(20)

Correct, now we have it :slight_smile:

But this thread alone shows the basic problematic of Patterns plus adverbs quite clearly:
If some elder users and an engaged newer user are making errors here and there on the way to possible solutions including adverbs, there might be an issue (and I don’t mean a bug).
My assumption: adverbs are a nice and short syntax, something syntactically rather pure itself – when going to apply it to the Pattern world it ends up with a lot of special cases which blurs the purity. Although I like it very much: the Pattern system is not perfect, it represents a development history and includes, as you are continuing to point out, a lot of inconsistencies, doublings etc. This is a conflict with the mathematical purity of the adverbial approach.
I can tell that I don’t remember a single case where I could say: “brilliant possibility to use adverbs, it makes things clearer, better readable, musically more fruitful”. And if a syntax is lacking such I would rarely use it and prefer to spend my time with other things.
That’s no argument of course against rethinking adverbs in general -

There’s no other adverb besides x that works for streams anyway (unlike for arrays). But even conceptually, it’s not terribly clear how some (other) should work for streams. “Regular” Pbinops (that use BinaryOpStream) actually behave as if the s adverb is being used all the time. See my related/linked thread for some more musings.

Right, in my memory I was including cases with adverbs used in Pfunc, Pcollect etc. also. But same here: the exceptional usages make an advantage of syntactic sugar questionable. I’m a bit arguing against my convention, some sytactic sugar I find very useful (like partial application), maybe personal preference, maybe linked to the frequency of occurence.

Over here I said: " I suppose one could implement .t for patterns by pulling all the stream values from the right operand into an array before doing the operation. That would hang the interpreter if the right-hand operand is infinite-length however – which is common for patterns, hence risky."

.t at least is conceptually not outrageous, but IMO too dangerous. Even I made the mistake of putting an infinite pattern to the right of +.x – which in that case meant only that the left pattern would draw one value only. For +.t, the same mistake would be fatal.

Also patternA + patternB.clump(n) would do something like .t, but without the risk… coming back around to Daniel’s point (that adverbs may be less useful than they initially seem).

hjh

1 Like

If instead (or in addition to) BinaryOpXStream there was a pattern called perhaps

PforEach(outterPatten, innerPatten, itemCombineFunc)

it would probably be less confusing how to use, and not much more verbose, e.g.

PforEach(Pseq((60..65), inf), Pseq([0, -3, 3]), (_+_))

There’s actually something a bit like that with Psetp (and its subclasses, Paddp etc.) which uses the end of the value sub-stream to “advance” (actually repeat) the main stream, except it only works for event streams and the combine action is limited to one field in the event as well as being “hardcoded” in the class name (Psetp only does “overrides”, Paddp only additions etc.)

PforEach could even work for events e.g.

PforEach(Pbind(\dur, 0.2), Pbind(\degree, Pseq([1, 2, 3]), (_.putAll(_)))

or even with more fancy actions like {|oev, iev| oev.blend(iev, 0.2)}

Actually I’ve been using something like that but in a “big Prout” that pulls from streams based on more triggers than simply just the innerPattern hitting nil. Perhaps a more general way would be to add a separate signalling stream/pattern that decouples the value giving of the innerPattern from the mechanism that advances outterPatten, i.e. something like Pwalk-Pgate combo but with “outer” and “inner”-patterns instead of lists and index patterns. In fact the distinction between outer and inner becomes blurry once you add separate triggers for advancing them separately. You’re just combining two (or more) patterns then. But that probably should be separate from the simpler PforEach. (Actually, the blendFrac value is pulled from another stream in my app/use-case. So technically, I’m combining 3 streams: two events and one blendFrac value-stream.)

This just a sketch, but the basic idea seems to work

PforEach : Pattern {
	var <>outerPattern, <>innerPattern, <>itemCombineFunc;

	*new { arg outerPattern, innerPattern, itemCombineFunc;
		^super.newCopyArgs(outerPattern, innerPattern, itemCombineFunc)
	}

	embedInStream {  arg inval;
		var outerStr = outerPattern.asStream, innerStr = innerPattern.asStream;
		// could treat itemCombineFunc as a pattern too, but it's not clear if that helps much
		// since functions can pull from streams "on their own" anyway...
		var outerVal = outerStr.next(inval), innerVal;

		if (outerVal.isNil) { ^nil; };
		loop {
			innerVal = innerStr.next(inval);
			if (innerVal.isNil) {
				outerVal = outerStr.next(inval);
				if (outerVal.isNil) { ^nil; };
				innerStr.reset;
				innerVal = innerStr.next(inval);
				 // PforEach(1, nil, {}) would hang without next check
				if (innerVal.isNil) { ^nil; };
			};
			// Copy prevents innerVal from changing the stored outerVal.
			// Well, to some extent, it's not a deep copy.
			inval = yield(itemCombineFunc.value(outerVal.copy, innerVal));
			// Maybe outerVal, despite being "fixed" until innerVal is nil shold still be
			// updated by inval, i.e.: combin(outerVal.copy.next(inval), innerVal).
			// Perhaps this could be an option in the pattern's constructor.
		};
	}

	// TODO: handle cleanups (for event streams)
	// todo: storeOn
}

Some testing of that

PforEach(Pseq((60..65), inf), Pseq([0, -3, 3]), (_+_)).iter.nextN(20)

r = PforEach(Pbind(\dur, Pseq([0.6, 1.7])), Pbind(\degree, Pseq([1, 2])), (_.putAll(_))).iter
r.nextN(3, ());
r.nextN(3, ()); // ends on finite outer

(r = PforEach(
	Pbind(\dur, Pseries(0, 0.1)), 
	Pbind(\degree, Pseq([1, 2]), \dur, Pseq([0.5, 1])),
	(_.blend(_, 0.5))).iter)
r.nextN(3, ());
r.nextN(3, ());

// "hacks" modifiable outer sub-objects, despite (shallow) copy
(r = PforEach(
	Pbind(\farr, [11, 22]),
	Pseq([1, 2]),
	{|e| e[\farr][0] = 66} ).iter.nextN(3, ()))

I’m not entirely happy with that because Pseq allows some “hacks” to continue past nil like

Pseq([1, 2, nil, 3, 4]).iter.all // -> [ 1, 2 ]
// but...
Pseq([1, 2, nil, 3, 4]).iter.nextN(6)
// -> [ 1, 2, nil, 3, 4, nil ]

But you can’t use the latter with the above PforEach (nor with BinaryOpXStream for that matter) because of the reset these do. It’s actually possible to write a stream that changes values on reset, but it’s tricky:

~cnt = -1
r = Plazy{var parr = [Pseq([1,2]), Pseq([3, 4])]; ~cnt = ~cnt + 1; parr @@ ~cnt}.iter

r.nextN(4) // -> [ 1, 2, nil, nil ]
r.reset
r.nextN(4) // -> [ 3, 4, nil, nil ]

So I’m thinking that a version of PforEach that also takes as argument a custom reset-decision function could be somewhat useful too as it could allow using “hacky” Pseqs that yield nils mid-stream.