How to use multiple Psegs for one parameter?

Hi all

I would like to control the \dur parameter of a Pbind with multiple Psegs so that when one Pseg has finished the next will start.
I tried it like this:

(
Pbind(
    \note,  Pseg( Pseq([1, 5],inf), Pseq([4, 1],inf), \linear),
    \dur, (Pseg(Pwhite(1.5, 2,5), 20, \wel) ++ Pseg(Pwhite(2.5, 6), 20, \lin)).trace,
).play;
)

But judging on the values “.trace” outputs it never changes to the next Pseg.
How could I do this?

Thanks

First, your spacing confused me a little. Pwhite(1.5, 2,5) almost looked to me like Pwhite(1.5, 2.5), which would have been an infinite stream :laughing: – but I did realize, this is actually length=5.

Pseg spreads out each “levels” value over time. In the first Pseg, there will be a start value from Pwhite, and then it will make an envelope segment to the second value taking 20 beats, then the third value 20 beats later, and so on, reaching the fifth and final value at 80 beats.

Maybe you expected the transition to occur quickly. It won’t – by definition, if you don’t let it run for at least 80 beats, then you won’t see the second pattern.

I scaled everything down by a factor of 10 because I didn’t want to wait that long – this one definitely does shift to the second Pseg, after 8 beats or so.

(
Pbind(
    \note,  Pseg( Pseq([1, 5],inf), Pseq([4, 1],inf), \linear),
    \dur, (Pseg(Pwhite(1.5, 2, 5) * 0.1, 2, \wel) ++ Pseg(Pwhite(2.5, 6) * 0.1, 2, \lin)).trace,
).play;
)

hjh

Ah, thanks for your explanation. Definately some confusion on my side :wink:

So what I actually intended to do is to have random values - Pwhite(1.5, 2) - for the \dur which would end after a certain amount of time and change to a different set of random values - Pwhite(2.5, 6) -.

Probably there’s a better way to do that than what I tried.

http://doc.sccode.org/Classes/Pfindur.html

See also Pattern Guide 06a: Repetition Constraint Patterns | SuperCollider 3.11.1 Help [sic]

hjh

Ah, yes Pfindur should do it.

I am just still confused. I tried this code:

(
Pbind(
    \note,  Pseg( Pseq([1, 5],inf), Pseq([4, 1],inf), \linear),
	\dur, (Pfindur(20, Pwhite(1.5, 2)) ++ Pfindur(20, Pwhite(2.5, 6))).trace,
).play;
)

And I get this error:

-> an EventStreamPlayer
( 'dur': Rest(1.8973808884621), 'delta': 1.8973808884621 )
ERROR: Message '*' not understood.

Oh, that’s my mistake: Pfindur is for event patterns. For number patterns, it should be Pconst.

Both of those classes will truncate the last value so that the total is exactly the given number (20). So, if the running total is 1.996, then the final value would be 0.004. If you don’t want a smaller final value, you could do Pconst(20, Pwhite(1.5, 2)).reject(_ < 1.5) – then the total could be a bit less than 20.

hjh

Cool, that’s what I was looking for!

Thanks a lot!

(
Pbind(
    \note,  Pseg( Pseq([1, 5],inf), Pseq([4, 1],inf), \linear),
	\dur, (Pconst(10, Pwhite(1.5, 2)).reject(_ < 1.5) ++ Pconst(15, Pwhite(2.5, 6)).reject(_ < 2.5)).trace,
).play;
)

I have one more question:

So now I have these two “Pconst”. I would like to be able to use two “Pconst” with a different configuration (different "Pwhite"s) for another parameter (not \dur), but that it will proceed to the next “Pconst” simultaneously with the \dur Pconst.

Hope it’s clear… How could I do this?

I’d use a structure like this:

Pchain(
    Pbind( /* other unrelated parameters */ ),
    Pseq([
        Pfindur(20, Pbind( /* one Pwhite pair */ )),
        Pfindur(20, Pbind( /* second pair */ )
    ], inf)
)

That is, the 2 Pwhite sequences will need to be bound together and terminated by Pfindur – but this should be isolated from other parameters that aren’t following a timed flow of control.

Also, that structure assumes that \dur will be one of the pairs under Pfindur. If it’s one of the “other parameters,” then the two arguments to Pchain should be swapped.

hjh

Hmm… So I tried it like this:

(
Pchain(
    Pbind(
		\note, Pseg( Pseq([1, 5],inf), Pseq([4, 1],inf), \linear),
	),
    Pseq([
		Pfindur(20, Pbind(\dur, (Pconst(10, Pwhite(1.5, 2)).reject(_ < 1.5) ++ Pconst(15, Pwhite(2.5, 6)).reject(_ < 2.5)) )) ,
		Pfindur(20, Pbind(\amp, (Pconst(10, (Pwhite(0.05, 0.2)) ) ++ Pconst(15, Pwhite(0.6, 0.9)) )) )], inf).trace
).play
)

Probably I did something wrong, but it doesn’t quite work the way I was looking for, at least when looking at the traced values.

So now I have the \dur and the \amp parameter. I would like the \amp parameter to change to the second Pwhite pair at the same time the \dur parameter changes to the second Pwhite pair, so that it follows the \dur and is executed in parallel.

But that isn’t the structure that I suggested.

Pfindur takes over the role of the time control, so there is no need for Pconst anymore.

And the Pseq wrapping the two Pfindurs takes the place of ++, definitely do not concatenate inside each element.

It was completely intentional to leave Pconst out of the outline :wink:

Not at the computer now so it’s quite inconvenient to try to edit code… perhaps later.

Anyway… Here’s the concept:

  • If you want one \dur Pwhite and one \amp Pwhite to be flow-controlled together as a unit, then these need to be bound up into one pattern. That’s the inner Pbind. So these inner Pbinds are not one for dur and one for amp. They are: one for the combination of the first dur pattern and the first amp pattern, and the other for the second of those.

  • You want one of those Pbinds to be in force for 20 beats, and the next for 20 beats: Pfindur for each.

  • You want to run one of those to completion, then the other one: Pseq. (In fact, ++ for patterns is implemented as a Pseq. I avoid writing ++ for patterns because it’s unclear: hides what is going on.)

Then Pchain gloms that time-controlled structure onto independent parameters.

hjh

(
Pchain(
    Pbind(
		\note, Pseg( Pseq([1, 5],inf), Pseq([4, 1],inf), \linear),
	),
    Pseq([
		Pfindur(20, Pbind(\dur, Pwhite(1.5, 2), \amp, Pwhite(0.05, 0.2))).reject { |ev| ev.delta < 1.5 },
		Pfindur(20, Pbind(\dur, Pwhite(2.5, 6), \amp, Pwhite(0.6, 0.9))).reject { |ev| ev.delta < 2.5 }
    ], inf).trace
).play
)

I think… I hope I got all the brackets right.

hjh

Ah, I see. Would have done it wrong again :sweat_smile:

Thanks a lot for your thorough explanation!

Hmm… there’s yet another question I have.

So now I would like to use two SynthDefs, one that would produce “the main sound” and one fx.

(
SynthDef(\sine, {

	arg freq = 300, out;

	var env, snd;

	env = EnvGen.ar(Env([0, 1, 0], [0.1, 0.3]), doneAction: 2);

	snd = SinOsc.ar(freq, mul:0.3);

	snd = snd * env;

	Out.ar(out, snd);

}).add
)


(
SynthDef(\rev, {

	arg in, revtime, out;

	var input, rev, revenv, revsnd;

	input = In.ar(in, 1);

	revenv = EnvGen.ar(Env([0, 1, 0.5, 0], [2, 3, 2]), doneAction: 2);

	rev = GVerb.ar(input, 10, revtime);

	rev = rev * revenv;

	revsnd = Splay.ar([input + rev]) * 0.3;

	Out.ar(out, revsnd);

}).add
)

~fx = Bus.audio(s, 1);


(
p =

Ppar(
	[
	Pbind(
			\instrument, \sine,
			\out, ~fx,
			\dur, Pseq( [Pconst(10, Pwhite(1.5, 2)).reject(_ < 1.5), Pconst(15, Pwhite(2.5, 6)).reject(_ < 2.5) ], 1).trace,
		),

	Pbind(
			\instrument, \rev,
			\in, ~fx,
			\revtime, 5,
)

];
)
)

p.play

So again the \dur parameter changes after some time. I would like to change the \revtime parameter in parallel, but with different Pwhites. I can’t quite figure out how to write this.

Now it’s getting difficult :laughing:

I can’t quite work it all out while riding the bus, but I think the problem will get a lot simpler if we can guarantee 20 beats for each phrase. Because – if both layers are guaranteed to match the total exactly, then you can simply write

Ppar([
    Pseq([
        Pfindur(20, synth Pbind 1),
        Pfindur(20, synth Pbind 2)
    ], inf),
    Pseq([
        Pfindur(20, fx Pbind 1),
        Pfindur(20, fx Pbind 2)
    ], inf)
])

Binding multiple durations for separate Ppar branches into the same Pfindur will produce other bugs – I considered but rejected that solution.

The problem with constraining the sum of Pwhite values (btw why did you go back to Pconst? since we know that won’t meet your requirement for some other parameters) is that the sum of random numbers may end up being just below the desired total, requiring a small value to fill the gap. So maybe this is the wrong approach in general.

Another way is to think of randomly partitioning the 20 beats according to some criteria. I’m not sure if a pattern object exists out-of-the-box for this, though there is a partition method for numbers (I think) – with that, you might be able to hack it by Plazy { Pseq(total.partition(... args...), 1) } – untested…? That may even remove the need for Pfindur.

EDIT: The partition method doesn’t do exactly what’s needed here. So you’d have to develop one – which may not be easy, but it’s just part of the algorithmic composition game: If you need math op ‘x’ and they didn’t give it to you directly… build it.

hjh

Actually I didn’t have a clear intention to use Pconst or Pfindur. Just tried to write something basic that could illustrate the problem.

Do you know some tutorials I could work through that would help me to learn how to develop one myself?
Seems like a big new challange to me :sweat_smile:

Actually, on second thought, partitioning would be harder than it seems. (Say you want the random numbers between 3.0 and 3.2, and the total to be 10.0. If there are three random numbers, the largest you could get is 9.6; if there are four, the smallest would be 12 – so, there exists at least one constraint that is impossible to solve – therefore, a solution can’t be guaranteed, not without some heuristics to go outside the strict bounds.)

But then I thought of an alternate design.

“I would like to change the \revtime parameter in parallel, but with different Pwhites”

I finally realized that, effectively, this is dividing the timeline into blocks. Trying to have each parameter independently transition from one block to the next is messy – it would have been manageable for multiple parameters following the same rhythm, but when you introduced the idea of rhythmic polyphony, then it became extremely difficult.

Instead, you could have one parameter that identifies the parameter set for the current time block. Then, each parameter independently looks up the right source pattern based on that ID.

(
// to show the logic more clearly,
// I'll abstract the parameters out
// also scaling by 1/4 b/c I don't want to wait that long
var synthDurs = [Pwhite(1.5, 2, inf), Pwhite(2.5, 6, inf)] * 0.25;
var synthAmps = [Pwhite(0.05, 0.2, inf), Pwhite(0.6, 0.9, inf)];
var fxDurs = [Pwhite(3.0, 6.0, inf), Pwhite(6.0, 9.0, inf)] * 0.25;

// introduce another parameter so the shifts are easier to hear
var octaves = [5, 6];

p = Pchain(
	Ppar([
		// synth
		Pbind(
			\dur, Pswitch1(synthDurs, Pkey(\phraseIndex)),
			\amp, Pswitch1(synthAmps, Pkey(\phraseIndex)),
			\octave, Pswitch1(octaves, Pkey(\phraseIndex)),
			\degree, Pwhite(-7, 0, inf),
			\pan, -0.7
		),
		// fx - for laziness in the example,
		// I'll just run another synth in the other channel
		Pbind(
			\dur, Pswitch1(fxDurs, Pkey(\phraseIndex)),
			\amp, 0.15,
			\octave, Pswitch1(octaves, Pkey(\phraseIndex)),
			\degree, Pwhite(0, 7, inf),
			\pan, 0.7
		)
	], 1),
	// current time block is 0 or 1
	Pbind(\phraseIndex, Pstep(Pseq([0, 1], inf), 5))
).play;
)

This sometimes happens in programming – the most convenient solution for a smaller-scale problem (in this case, time-constraining one stream of random durations) could scale up slightly (to the case of time-constraining two streams according to the same rhythm), but simply cannot scale up to a more general problem (time-constraining any number of streams with any number of simultaneous rhythms).

There’s not really any way out except for a redesign – but the benefit is that this new design is not conceptually more complex for 3 streams vs 300.

If you didn’t want to wrap them up in Ppar, you could also run a Pdef(\phraseControl, Pbind(\phraseIndex, Pseq(...), \dur, 20).collect { |ev| Pdefn(\phraseIndex, ev[\phraseIndex]); ev }) and then use Pdefn(\phraseIndex) in any independently running pattern, anywhere.

hjh

Wow, that’s amazing!
Thanks a lot!