Arrays of different lengths

I’m having trouble figuring out how to dynamically generate Arrays of different lengths in my Pbind.
i thought this, having a Prand as stream that i then call from within the Array.geom as the number of items would be ok, but obviously i’m missing something.

(
~times = Prand([20, 50], inf).asStream; // number of items to generate later
a = Pbind(\type, \midi, \chan, 7, \midicmd, \noteOn, \midiout, x, \midinote, 44, \amp, 1,
\dur, Pseq(Array.geom(~times, 0.015, 1.1), inf) //  
).play;
)

I tried using a Plazy but i don’t think it’s doing what i want, as the number of items seems stuck so obviously i’m doing something wrong here too:

(
~times = Plazy(Prand([20, 50], inf)).asStream;
a = Pbind(\type, \midi, \chan, 7, \midicmd, \noteOn, \midiout, x, \dur, Pn(Pgeom(0.5, 0.88, ~times), inf), \midinote, 44, \amp, 1).play;
)

THANKS

The first one won’t work because the first argument of geom is expected to be something number-like but definitely not a stream. The implementation of geom:

	*geom { arg size, start, grow;
		var i=0;
		var obj = this.new(size);
		while ({ i < size },{        // <------------- 
			obj.add(start);
			start = start * grow;
			i = i + 1;
		});
		^obj
	}

For me, your second version seems to work as expected? With this:

(
~times = Plazy(Prand([20, 50], inf)).asStream.trace(prefix:"  count: ");
a = Pbind(
	\dur, Pn(Pgeom(0.5, 0.88, ~times), inf).trace(prefix:"    dur: "), 
).play;
)

… I definitely see 20 and 50 being picked randomly each time the pattern finishes. I was actually expecting this not to work, so I’m a little surprised?

In general, this case is weirdly hard to handle in SuperCollider - I struggle with this all the time. Plazy IS good for precisely the case you’re looking at: that is, repeating the same pattern with a different count / length argument each time. This is because MOSTLY the P____ classes expect an integer for their length argument, and can’t handle a stream.

But, I think in your case, Plazy isn’t really doing anything. Plazy calls .value on whatever is passed into it, and then starts .yield-ing it’s values as a stream. When that stream runs out of values, it calls .value on the original object and begins yielding again. You pass in a Prand - calling Prand().value just returns the Prand, so it starts yielding values from that. The Prand has a length: inf, so it never runs out - effectively, wrapping an infinite pattern in a Plazy does nothing, because the pattern just provides values forever.

One way to do this is to simply choose a random length inside the function you pass to Plazy. This will get executed again each time the Pgeom runs out of values, and should give you what you expect.

(
Pbind(
	\dur, Plazy({
		Pgeom(
			0.5, 0.88, 
			length: [20, 50].choose.postln // no need for a pattern, just pick one randomly
		)
	}).repeat.trace // Plazy needs .repeat, else it only plays the pattern through once
).play
)

You can also pull from a stream in place of the [].choose, as you were doing before, though for me this feels a little less clean because then you’re needing to track extra streams in a variable somewhere.

I feel SURE that there’s another better way to express this, but I just can’t quite come up with it. I’ll post if I can think of something…

2 Likes

thanks so much scztt
and i must say i totally missed that .repeat on Plazy until now. that’s a first for me, so i’ll be using this from now on!!
cheers

I am trying something relatively similar but running into a problem. I want to change some CC value on my synth every time the Plazy triggers a new duration sequence, and stick to that CC for the whole sequence, until Pgeom is asked to make a new one. Every x seconds the Task below produces new values for the Pgeom:

(
t = Task({
    inf.do({
~steps = rrand(22, 100).postln;
~start = rrand(0.4, 0.69).postln;
~geoGrow = ~steps.linlin(22, 100, 0.99, 0.981).postln;
"----------------".postln;
~durSequence = Array.geom(~steps, ~start, ~geoGrow);
~durSequence.sum.wait;
	});
});
)

Pdefn(\dur, Plazy({
	       Pgeom(
			~start,
			~geoGrow,
			~steps
		)
	}).repeat)

(
t.start;
Pbind(
	\dur, Pdefn(\dur).trace
).play
)

// here's the MIDI CC part, which does not work as i wanted:
~cc = Pbind(\type, \midi, \chan, 0, \midicmd, \control, \midiout, m, \ctlNum, 92, \control, Pstutter(~steps, Pseq([0, 50, 70, 90, 127], inf)).trace, \dur, Pdefn(\dur)).play;

Any insight will be great.
Thank you dearly

I am trying something relatively similar but running into a problem. I want to change some CC value on my synth every time the Plazy triggers a new duration sequence, and stick to that CC for the whole sequence, until Pgeom is asked to make a new one. Every x seconds the Task below produces new values for the Pgeom:

then how about just moving the midi sending code inside your Plazy’s function?

(
t = Task({
	inf.do({
		~steps = rrand(22, 100).postln;
		~start = rrand(0.4, 0.69).postln;
		~geoGrow = ~steps.linlin(22, 100, 0.99, 0.981).postln;
		"----------------".postln;
		~durSequence = Array.geom(~steps, ~start, ~geoGrow);
		~durSequence.sum.wait;
	});
});
c= Pseq([0, 50, 70, 90, 127], inf).asStream;  //pattern with cc values
m.latency_(s.latency);  //not to forget
)

Pdefn(\dur, Plazy({
	var ccval= c.next;  //ask for next value
	("ccval:"+ccval).postln;
	m.control(0, 92, ccval);  //send out control change here
	Pgeom(  //create and return a new Pgeom
		~start,
		~geoGrow,
		~steps
	)
}).repeat)

(
t.start;
Pbind(
	\dur, Pdefn(\dur).trace
).play
)

_f

#|
fredrikolofsson.com musicalfieldsforever.com

1 Like

Fantastisk!

Can you elaborate on what this line does?


m.latency_(s.latency); 

thank you dearly

This is a tricky one… I struggle with doing variations of this all the time. I’ll share a technique that I use for composing multiple patterns together, but only pulling values from some them occasionally - in your case, you want a long phrase consisting of the Pgeom sequence, but you only want to pull from the CC pattern once per this entire phrase… An example with comments:

(
// Change this to a routine, since we want to pull it in our Plazy via .next
t = Routine({
	inf.do({
		~steps = rrand(22, 100).postln;
		~steps = 4;
		~start = rrand(0.4, 0.69).postln;
		~geoGrow = ~steps.linlin(22, 100, 0.99, 0.981).postln;
		"----------------".postln;
		~durSequence = Array.geom(~steps, ~start, ~geoGrow);
		~durSequence.sum.wait;
	});
});
)

(
// A utility pattern: produce one "true" and then "false" forever.
// Use this to signal to downstream patterns when this stream has restarted.
Pdefn(\restarter, Pseq([true]) ++ Pseq([false], inf));

// Produce a finite series of durs (n = ~steps).
// Use the \restart key to store when we are starting a new
// sequence of \durs
Pdef(\geoDur, Pbind(
	\restart, Pdefn(\restarter),
	\dur, Plazy({
		t.next();
		Pgeom(
			~start,
			~geoGrow,
			~steps
		)
	}).trace
));

// CC event stream
Pdef(\cc, Pbind(
	\type, \midi, 
	\chan, 0, 
	\midicmd, \control, 
	\midiout, m,
	\ctlNum, 92, 
	\control, Pseq([0, 50, 70, 90, 127], inf)
).trace)
)

(
// 1. Compose Pdef(\cc) and Pdef(\geoDur) with <>
// 2. Use Pclutch to grab the next value from Pdef(\cc) ONLY when \restart is true
// 3. Since \geoDur is finite, we use .repeat to restart it when it runs out of
//    \durs. This will also reset Pdefn(\restarter).

Pdef(\output, 
	Pclutch(Pdef(\cc), Pkey(\restart)) 
	<> Pdef(\geoDur).repeat
).play
)

Basically, in \geoDur we have a key \restart which is true once and then false afterwards. Because we make \geoDur finite in length, it plays itself out for one phrase and ends. Wrapping this in a .repeat causes it to restart, which runs our Plazy again and gives us another true in the \repeat key. Since we <> compose this with \cc, we can access the \restart key with Pkey(\restart) and feed it into Pclutch so that we ONLY pull a new CC value once when \geoDur resets.

1 Like

I cleaned up and added comments to some recent code if anyone is interested - this is a more complex version of this design pattern where patterns in individual keys (in this case, \degree, \octave, and \lpf) only pull new values when a downstream pattern signals them to via a \stepOctave key.

In this case, the patterns for individual keys can be de-synchronized. It’s a little easier to do this by using something like Pstutter - BUT stutter requires a fixed number of repetitions, which is a bit less flexible. For example, \step2 pulls the \lpf parameter randomly based on Prand.

Code
(
// A dumb pluck synth
SynthDef(\ring, {
	var in, trig, freq, sig, env, trigEnv;

	trig = \trig.tr(0);
	freq = \freq.kr(220).lag3(0.02);

	env = Env.adsr.kr(gate:\gate.kr(1), doneAction:2);
	trigEnv = Env.perc(0, 0.2, curve:\exp).ar(gate:trig);

	trig = Trig.ar(trig, SampleDur.ir);

	in = LFSaw.ar(freq * 3.015);
	in = in + WhiteNoise.ar(0.2);
	in = BLowPass4.ar(
		in,
		\lpf.kr(100),
		0.3
	);

	sig = Pluck.ar(
		in, trig,
		0.2,
		freq.reciprocal,
		8,
		\coef.kr(0.7)
	);
	// sig = (sig * 4).tanh;
	sig = sig - FreqShift.ar(
		sig, freq / (-2.015)
		// sig, freq / trigEnv.linlin(0, 1, -2.015, -1.8)
	);
	sig = LeakDC.ar(sig);
	sig = env * sig;
	sig = Pan2.ar(sig, \pan.kr(0), \amp.kr(1));

	Out.ar(\out.kr(0), sig)
}).add;

// Some simple parameter patterns
Pdefn(\octave, Pseq([3, 4, 3, 6, 5], inf));
Pdefn(\degree, Pseq([0, 4, 2, 9, -2], inf));
Pdefn(\lpf, Pexprand(80, 19000));

Pdefn(\dur, {
	Pseq([1]) ++ Pgeom(
		2/4,
		rrand(0.81, 0.84),
		[12, 16, 32, 64].choose
	)
});

// alt version, more controlled durations
// Pdefn(\dur, {
// 	(
// 		Pseq([1])
// 		++ Pgeom(
// 			1/4,
// 			1 + rrand(-0.001, 0.001),
// 			[12, 16, 32, 64].choose
// 		)
// 	)
// });

// Combine our param patterns and Pclutch them with restart keys
Pdef(\ringMono, Pmono(
	\ring,

	\amp, 		0.02,
	\dur, 		Pdefn(\dur).repeat,

	\scale, 	Scale.partch_o6,
	\octave, 	Pclutch(Pdefn(\octave), Pkey(\stepOctave)),
	\degree, 	Pclutch(Pdefn(\degree), Pkey(\stepDegree)),
	\lpf, 		Pclutch(Pdefn(\lpf), Pkey(\stepLpf)),
));

// One true and count-1 falses
Pdefn(\stepper, Plazy({
	|count|
	Pseq([true]) ++ Pseq([false], count - 1)
}).repeat);

// Different reset patterns for each parameter - these will be passed to the Pclutch
// of each of these streams.
Pdef(\step1, Pbind(
	\stepOctave, 	Pdefn(\stepper) <> Prand([24, 16], inf),
	\stepDegree, 	Pdefn(\stepper) <> Prand([3, 6, 9], inf),
	\stepLpf, 		Pdefn(\stepper) <> Pwhite(8, 15),
));

Pdef(\step2, Pbind(
	\stepOctave, 	Pdefn(\stepper) <> 8,
	\stepDegree, 	Pdefn(\stepper) <> 6,
	\stepLpf, 		Prand([true, false, false], inf),
));

Pdef(\stepSlow, Pbind(
	\stepOctave, 	Pdefn(\stepper) <> 64,
	\stepDegree, 	Pdefn(\stepper) <> 64,
	\stepLpf, 		Pdefn(\stepper) <> 64,
));

// Compose our patterns above, \ringMono and a step pattern,
// and four streams running in parallel panned.
// Vary some of the parameters (coef and freq) over time.
Pdef(\player,
	Ppar(
		4.collect {
			|i|
			Pbind(
				\pan, i.linlin(0, 3, -1.0, 1.0)
			)
			<> Pdef(\ringMono)
			<> Pdef(\stepSlow) // <-- try different restart patterns
		}
	)
	// .finDur(6).repeat // <-- reset everything every 6 beats

	<> Pbind(
		\coef, Pseg([0.9, 0.4, 0.9], [16, 16], \exp).repeat,
		\freq, Pkey(\freq) * Ptuple([
			1,
			Pseg([1, 1.03, 1], [16, 16], \exp).repeat
		])
	)
).play
)



3 Likes