Pseq Ending Trigger?

Hi -
I have a beginner question about patterns. I looked on the forum and in the documentation, but I’m not sure I understood everything, so I hope it’s OK to ask.

I am trying to find some type of indicator when a Pseq has reached the end of its sequence.
I realize I could do something like the following, but it seems like a rigid solution.

	Pbind(
		\freq, Pseq([300, 500, 900], inf),
		\dur, Pif(Pkey(\freq)>=900, 0.9, 0.1)).play;

This is a tricky requirement.

At the time of returning 900, it isn’t yet known that this is the end of that sequence. That will not become known until the next value is requested.

It’s a general problem with SC’s pattern system that, in most cases, patterns are completely unaware of future state. If you want to know that this is the end of the pattern without depending on a specific value, then it’s necessary to know right now that the next thing to happen will be to reach the end – but there’s no way to know, right now, what is the next thing.

So I don’t think there’s a better way than to check for some feature of the final value, as you did.

In your example, the “final check” requires the final value to be in a specific range. One way to improve on that might be mark the final value, for example, with a reference Ref object.

(
p = Pbind(
	// `900 is short for Ref(900)
	\freq, Pseq([300, 500, `900], inf)
	.collect { |freq, event|
		event[\final] = freq.isKindOf(Ref);
		freq.dereference  // leave only the plain number
	},
	\dur, Pif(Pkey(\final), 0.9, 0.1)
).play;
)

hjh

2 Likes

Otherwise, why not use Pgate/Pn in combination? It’s not as specific but it gets the job done: Pgate | SuperCollider 3.12.0 Help

1 Like

I had thought of that, but Pn with a key identifier marks the first value, not the last. If the intent, as in the given example, is to give a different rhythmic value to the final note of the phrase, Pn’s marker will come one event too late.

hjh

Thanks for the help. It’s interesting that with how powerful patterns are, there are some limitations and idiosyncrasies. Sending the trigger on the last event and the first event are both useful!

There are two small issues here that I’d like to ask about further.

The first is that the “final object” in my Pseq pattern is dynamic (passed with a Plazy) - so I’m not able to mark the value as a “Ref” manually. Is it possible to assign “Ref” in an function?

The second is that I’d like to use this “triggering” to evaluate another function/Plazy. The issue here is that Pgate/Pn only plays the first item in the new function. For example

b = Plazy({
    var x, y;
     x = Array.fill(5, {0.4.rand+0.1});
    Pseq(x, 3);
});

Pbindef(\ome,
	\degree, Pn(Pseq([0, 3, 9], 1), inf, \keyer),
	\dur, Pgate(Pn(b, inf), inf, \keyer).trace
).play;

Probably like this, if it’s always a Pseq:

b = Plazy({
    var x, y;
    x = Array.fill(5, {0.4.rand+0.1});
    // "normal" way is:
    // x[x.size - 1] = Ref(x[x.size - 1]);
    // but SC has a friendlier way
    x.putLast(Ref(x.last));
    Pseq(x, 3);
});

That is: 1. Build array. 2. Replace the last value with a Ref to the last value.

I think that’s a “trigger on first” rather than “trigger on last” thing, so Pn may be appropriate here.

Pgate is specifically for values that should not change within a phrase, so it wasn’t a correct suggestion here. Simply don’t use Pgate in this case. (Pn with a key and Pgate are often used together, but this doesn’t mean that Pn with a key must always have an accompanying Pgate.)

I think you don’t need the key at all. Just this may be enough:

b = { some code that returns a pattern };

p = Pbind(
    \dur, Pn(Plazy { b.value }, inf),
    ... more...
)

hjh

If I understand correctly, the Pn repeatedly executes the Plazy, which will be evaluating a function outside of the Pbind.
I think the question I have is how to have the Plazy executed only when another pattern (a Pseq) trips a Ref.

I’m going to take a step back and describe the execution flow of a pattern’s stream.

  1. You call .next.
  2. The stream resumes from the last point where it yielded a value.
  3. Now the stream does some processing that is relevant to the next value to be returned.
  4. It returns (.yields) the value, and pauses to wait for the next .next call.
  5. Back to 1.

At 3, patterns usually don’t look into the future. That is, the processing is for the one, single next value that will be returned.

The flow you’re trying to achieve is:

  1. You call .next.
  2. The stream resumes from the last point where it yielded a value.
  3. If a flag had been set previously: either end, or otherwise modify the behavior.
  4. Processing that is relevant to the next value to be returned.
  5. And the stream (upon a flag) sets up some future state that will affect what happens after the next .next call.
  6. .yield, and wait for the next .next call.

The point is that the logic in 3 and 5 doesn’t exist in most patterns.

So the conclusion is that it’s probably not helpful to formulate the question in terms of “how to have the Plazy executed only when another pattern (a Pseq) trips a Ref.” This is going to make the logic more complicated and easier to break.

The second thing is that Plazy does not evaluate the function for every .next.

(
Pn(
	Plazy {
		"evaluating Plazy now".postln;
		Pseq(Array.fill(3, { rrand(1, 3) }), 2)
	},
	inf
).trace.asStream.nextN(10)
)

evaluating Plazy now
3
3
1
3
3
1
evaluating Plazy now
1
3
3
1
-> [ 3, 3, 1, 3, 3, 1, 1, 3, 3, 1 ]

Here, Plazy produces a Pseq with a 3-item array, repeated twice – and indeed, you get six values before Plazy runs again.

So there isn’t a direct way to force Plazy to reset based on a flag from somewhere else. When the stream is in the middle of a pattern returned by Plazy, then Plazy has no control.

I’m not entirely clear what you’re trying to do, but I have a feeling that “disposable Pbind” might be a better approach. That is, many pattern examples put Pbind at the top, so then you might think that Pbind should always be at the top, and any other flow of control should be shoehorned into that. This is often too complicated, and expressed more directly by using a Pbind for one phrase.

What about…?

(
b = {
	var array = Array.fill(rrand(3, 10), { rrand(0.2, 0.8) });
	array.putLast(Ref(array.last));
	Pseq(array)
};

p = Pn(
	Pbind(
		// note, 'b' is finite --
		// when the Pseq ends, it will stop the whole Pbind
		\dur, Plazy { b.value },
		\degree, Pseries(0, 1, inf),
		
		// change something based on "last item"
		// note that this is a SEPARATE requirement from
		// the flow of control
		// this one can *only* be handled by marking the last value
		// the pattern reset can NOT be handled by marking the last value
		\octave, Pif(Pfunc { |ev| ev[\dur].isKindOf(Ref) }, 6, 5),
		
		// finally, make sure the 'dur' Ref doesn't break
		\dur, Pfunc { |ev| ev[\dur].dereference }
	),
	inf
).play;
)

hjh

1 Like