Repeating a random pattern


#1

Hello

I’m sure it’s trivial but I haven’t found out how to repeat a random number of times a random pattern from a list of patterns.

Example : I have two patterns Pseq([1,2]) and Pseq([3,4]) and I want to choose one of them, and repeat it 2 or 4 times.

I’ve tried Pn and Pstutter but neither of them does what I want

Pn(
	Prand(
		[Pseq([1,2]), Pseq([3,4])], 
	),
	Prand([2,4])
).asStream.nextN(10)
-> [ 3, 4, nil, nil, nil, nil, nil, nil, nil, nil ]
Pstutter(
	Prand([2,4]),
	Prand(
		[Pseq([1,2]), Pseq([3,4])], 
	)
).asStream.nextN(10)
-> [ 3, 3, nil, nil, nil, nil, nil, nil, nil, nil ]

Any clue ?

Thanks !


#2

The simplest what comes to my mind is the straight translation of your verbal formulation:

Pn([Pseq([1,2]), Pseq([3,4])].choose, [2, 4].choose).asStream.nextN(10)

By the way Pattern / Streams and repetitions is by far no trivial topic. I have encountered a number of examples, where the variant of repetition / embedding can be easily thought and formulated, but is clumsy to express in classical Pattern notation.
This is a hint that even the most flexible syntax (and SC patterns are definitely very flexible) tends to lead to certain solutions and makes others less likely to be used …


#3

Thanks a lot @dkmayer for your fast and simple answer !

I was expecting to repeat that process several times.
Based on your solution, I would do

Pn(
	Plazy(
		{
			Pn([Pseq([1,2]), Pseq([3,4])].choose, [2, 4].choose.debug("nb"));
		}
), inf).asStream.nextN(20)

which seems to work

nb: 4
nb: 2
nb: 4
-> [ 1, 2, 1, 2, 1, 2, 1, 2, 3, 4, 3, 4, 1, 2, 1, 2, 1, 2, 1, 2 ]

but maybe there’s something clever ?

Thanks again !


#4

Exactly, Plazy is then the tool of choice to embed it.

However, thinking about this, the above solution isn’t very flexible. Suppose you wouldn’t only want to choose (which is easy to write with method ‘choose’), but to generate a sequence of Patterns with Patterns itself and then repeat them, controlled with a sequence, which should maybe also be generated by a Pattern. How to do that ?
You could use Ref objects to force a Pattern to output Patterns, or, in other words, inhibit their normal embedding. This Stream of ref’ed Patterns can then be used in a Plazy:

// last example

(
x = Pstutter(
	Prand([2, 4], inf),
	Prand([`Pseq([1, 2]), `Pseq([3, 4])], inf)
).iter;

Pn(Plazy { x.next.value }).iter.nextN(20);
)

// check helper stream alone
// x.next

So more refined sequences of this type can be generated:

(
y = Pstutter(
	Pseq([Prand([1, 2]), Prand([3, 5])], inf),
	Pseq([ `Pseq([1, 2]), `Prand([3, 4]) ], inf)
).iter;

Pn(Plazy { y.next.value }).iter.nextN(30);
)

Insterestingly I don’t remember ever having thought about this kind of using Refs in combination with Patterns, it might open some nice possibilities.
Also it might be worth writing a Pattern for this special type of repetition.

// provisional easy implementation, might need improvements

save as .sc file in Extensions and recompile

PpatStutter : Pstutter {
	*new { |n, pattern|
		^super.new.n_(n).pattern_(pattern)
	}

	embedInStream { | event |
		var str = Pstutter(n, pattern).iter;
		inf.value(event).do { event = str.next.value.embedInStream(event) };
		^event;
	}
}

Then we can write

(
PpatStutter(
	Pseq([Prand([1, 2]), Prand([3, 5])], inf),
	Pseq([ `Pseq([1, 2]), `Prand([3, 4]) ], inf)
).iter.nextN(30)
)

(
PpatStutter(
	Prand([2, 4], inf),
	Prand([`Pseq([1, 2]), `Pseq([3, 4])], inf)
).iter.nextN(30)
)

#5

There’s a catch here – something that is poorly documented.

Let’s take Pseries(start, step, length) as an example: beginning with a number start, and producing length values, repeatedly add step.

A given series of numbers can have only one starting point, and one length.

But, within that series, conceivably the step could change. To support that, you’re allowed to provide a pattern for step. When the Pseries streams out, it automatically converts step into a stream.

Pseries(0, 1, 5).asStream.all
-> [ 0, 1, 2, 3, 4 ]

Pseries(0, Pwhite(1, 3, inf), 5).asStream.all
-> [ 0, 3, 5, 6, 8 ]

So, what if you use a pattern for the starting point?

Pseries(Pwhite(0, 7, inf), 1, 5).asStream.all
-> [ a Pwhite, a Pbinop, a Pbinop, a Pbinop, a Pbinop ]

Every time Pseries streams out, it needs one and only one starting point. So it does not automatically asStream, and you get the results of the math operations on the pattern objects themselves (basically, nonsense).

Pseries(Pwhite(0, 7, inf).asStream, 1, 5).asStream.all
-> [ 7, 8, 9, 10, 11 ]

^^ If you supply a stream explicitly, then it does as you expect.

One step further, to show why streams are useful here:

Pn(
	Pseries(  // <-- return values
		Pseries(0, 1, inf).asStream,  // <-- `start` stream
		1, 4
	),
	5
).asStream.all
-> [ 0, 1, 2, 3, 1, 2, 3, 4, 2, 3, 4, 5, 3, 4, 5, 6, 4, 5, 6, 7 ]

^^ The start stream remembers its state between invocations of the “return” pattern. Without the explicit asStream, that state is forgotten.

Back to your example: In a single invocation of Pn, there can be one and only one number of repeats. So Prand([2, 4]) needs .asStream.

Pn(
	Prand(
		[Pseq([1, 2]), Pseq([3, 4])], 
	),
	Prand([2, 4]).asStream
).asStream.nextN(10)
-> [ 3, 4, 3, 4, nil, nil, nil, nil, nil, nil ]  // did twice, OK

This does actually all make sense, except that sclang does nothing syntactically to distinguish which arguments are which type. You just have to “know.” That’s definitely a point of confusion when getting started with patterns.

hjh


#6

Sorry for the late reply @dkmayer and @jamshark70, your two last solutions are a bit difficult to understand for me ! I’m working on it today and will try to get it ! Thanks again !