Changing length of PSeq array on every repeat?

Say I have a pattern like Pseq([1,2,3,4], inf). I’d like the number of elements in the sequence to vary on each repeat, according to another pattern. On the first playthrough I want to play all 4 elements, then 3, 2, back up to 3, and then 4 again, and so on. How can I achieve this?

For example, I’ve been trying this:

p = Pbind(
	\degree, Pn(Pfin(Pseq([1,2,3,4]), Pseq([1, 2, 3, 4, 5, 6, 7])), inf),
	\dur, Pseq([0.4], inf),
	\sustain, 0.15

But on each repeat the pattern just plays the first 4 notes, or whatever number of elements happen to be in the array of the first sequence.

Pn(Pser((1..7), { rrand(2, 7) }), inf)

// or
Pn(Pser((1..7), Pwhite(2, 7, inf).asStream), inf)

Note that a pattern used as a number of repeats normally needs to have .asStream attached.


1 Like

Ah, the .asStream seems to be what I was missing.

This works for me:

p = Pbind(
	\degree, Pn(Pser((1..7), Pseq((7..2) ++ (2..7)).asStream), inf),
	\dur, Pseq([0.4], inf),
	\sustain, 0.15

More generally speaking, the repeats argument is usually evaluated with .value in the Patterns from classlib. That makes it possible to use “naked” functions for repeats but also streams/routines because .value for streams/routines is the same as .next. However .value for Patterns just returns the pattern.

r = r { 42.yield }; [r.value, r.value] // -> [ 42, nil ]
Pbind().value // -> a Pbind

Also .iter is a good shortcut synonym for .asStream on Patterns, but it can be a gotcha if you call it on an array if you didn’t intend to iterate over the array’s items but use it as a constant.

[1, 2] // -> 1
[1, 2] // -> [ 1, 2 ]

But in your case

Pn(Pser((1..7), Pseq((7..2) ++ (2..7)).asStream), inf)

can be just

Pn(Pser((1..7), ((7..2) ++ (2..7)).iter))

(inf is the default repeats for Pn, so that’s redundant too.)

Also there are already defined methods for mirroring arrays

(7..2) ++ (2..7) == (7..2).mirror2

And the array pyramid methods can “directly” generate the same number patterns you want (in finite form of course), although it’s hard to remember the right invocations; their argument is an algorithm number!

n = 4 // use 7 for your original ex.; this is for shorter output
a = (n..2).mirror2
g = Pn(Pseries(1, 1, a.iter), a.size).iter.all // or
h = (1..n).pyramidg(3).drop(-1).mirror2.flatten
g == h // test

// pyramid(7) almost does what you want by itself
// but you have to remove the middle element
k = (1..n).pyramid(7); k.removeAt(k.size div: 2); k
k == g

There are also list comprehensions (actually generators as they return routines) in SC… which make some of that pyramid business more comprehensible …

// same as
{: (1..x), x <- (n..2) }.all

{: (1..x), x <- (n..2).mirror2 }.all.flatten
// same as

And a rather obscure/undocumented way of doing the same without flatten

{{; (1..x) do: _.yield, x <- (n..2).mirror2 }}.r.all 

Also this is a “flatMap” application, but then so all the pyramid functions basically…


As a variation on that, “auto-flattening” can be done by producing a Pseq that has an array of Pseqs as its argument.

Pseq((n..2).mirror2.collect{|x| Pseq((1..x))}).iter.all 

In that last expression, using Pseries(1, 1, x) probably marginally more efficient than Pseq((1..x)) for longish things. On that angle though, the Pn(Pseries solution is even more efficient though; properly encapsulating the a variable looks something like

Plazy{var a = (n..2).mirror2; Pn(Pseries(1, 1, a.iter), a.size)}.iter.all

You can avoid generating/storing the array a too, although that looks pretty long to write as a constant-storage Pattern:

	var itr = (Pseries(n, -1, n-1) ++ Pseries(2, 1, n-1)).iter;
	Pn(Pseries(1, 1, itr), 2*n-2)

Premature optimization :sweat: As it turns out, SC list comprehensions automatically detect and apply this kind of optimization.

Trying to do something similar here, but noticing that this bit of code crashes my server when it reaches the end of the sequence. Same when substituting:

Pn(Pser((1..7), ((7..2) ++ (2..7)).iter))

Can anyone confirm this and/or suggest an alternate route?