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
).play
)
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.
hjh
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
).play
)
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].iter.next // -> 1
[1, 2].asStream.next // -> [ 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 …
(1..n).pyramidg(3).drop(-1)
// same as
{: (1..x), x <- (n..2) }.all
{: (1..x), x <- (n..2).mirror2 }.all.flatten
// same as
(1..n).pyramidg(3).drop(-1).mirror2.flatten
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…
(n..2).mirror2.collect((1.._)).flatten
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:
(
Plazy{
var itr = (Pseries(n, -1, n-1) ++ Pseries(2, 1, n-1)).iter;
Pn(Pseries(1, 1, itr), 2*n-2)
}.iter.all
)
Premature optimization 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?