How to bind pattern params within a conditional?

I have two synths that make use of the DynKlank UGen. In the first, the resonant frequencies, amplitudes and ring times are fixed; in the second the synth is initialised with empty arrays. At the moment both these synths are addressed each by its own Pbind. The two Pbinds are very similar except that in the second the resonant frequencies, amplitudes and ring times are supplied within its respective pattern.

For the sake of brevity I would like to combine the two patterns; this is proving to be a challenge. Put simply: I would like to bind or leave out pattern parameters according to the result of a conditional thus:

Pbind(
 \instrument [instA, instB].choose,
 \param2, value,
 \param3, value,
 \param4, value,
 \resonantFreqs, Pfunc(|e| (e[\instrument] == instA).if{
  	\dkFreqs, value;
  	\dkAmps, value;
  	\dkRT, value}{'return dummy value'},	
 \param6, value, 
 etc........
 })

When I’ve tried the above I have not been able to get the results of the conditional to return their values. i.e.in the above example dkFreqs returns nil. I have tried various method to accomplish this including inserting variables into the environment at a higher level but everything I’ve come with results in more complications than simply repeating the two Pbinds in full. I want to code DRY. Is there an elegant way of achieving this? Thanks…

Does that even work at all?

r = Pbind(\dur, 0.2, \blah, Pfunc { |e| e[\freq] = 550 }).asStream
r.next(())

outputs an infinite structure:

-> ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( 'blah': ( ...etc...

Well, it’s not actually infinite, but \blah points back to the event itself…

v = r.next(())
v[\freq] // -> 550

The way you had the assignments inside the Pfunc was surely totally ineffective, as you’ve used the Pbind syntax inside a Pfunc.

If you try to access the infinite data loop at top level like this

v[\blah] // interpreter hang

On the other hand you can return some new event and/or use the Pfunc [just] for side effects to change the main event. If you create a non-empty sub-event, you can e.g. use a callback to evaluate it, but there are quite a few other possible approaches, and I can’t say at more at this point.

(p = Pbind(
	\dur, Pn(0.5, 3), \amp, 0.2, \legato, 0.2,
	\blah, Pfunc({|ev| ev[\freq] = 660; (dur: ev[\dur] * 3, freq: 220, amp: 0.4)}),
	\callback, { ~blah.play }
))

p.asStream.next(())
// -> ( 'legato': 0.2, 'dur': 0.5, 'callback': a Function, 'amp': 0.2, 
//  'blah': ( 'amp': 0.4, 'freq': 220, 'dur': 1.5 ), 'freq': 660 )

q = p.play
q.stop

In synth1 the DynKlank frequencies etc. are hardcoded into the SynthDef; in synth2 DynKlank is initialised with blank arrays: [0!n ]which is filled via use Control.names from the values in its relevant pattern. All works when two independent patterns are used; nevertheless it’s verbose to use two Pbinds that it many respects have the same elements.

It 's always difficult to know whether to post the original code. In this case the original code makes reference to a number of custom classes that could obscure the essential issue which is (leaving DynKlank out of it altogether) how to bind pattern arguments in a conditional. Thank you RFluff you’ve given me ideas to play with here. If I’m still struggling I’ll post the original…

Have a look at these threads for some concrete examples on some forms of sharing between Pbinds:

I can’t say more at this level of detail you’ve provided.

1 Like

Hi,

from what you’ve posted so far I don’t understand why you need the condition at all. If one SynthDef doesn’t have these args (as numbers are hard-coded) they will simply be ignored.

(
e = Env.perc(0.01, 0.2);

SynthDef(\a, { |freq = 400, amp = 0.1| 
	Out.ar(0, Pulse.ar(freq, 0.5) * EnvGen.ar(e, doneAction: 2))
}).add;

SynthDef(\b, { |freq = 400, width = 0.5, amp = 0.1| 
	Out.ar(0, Pulse.ar(freq, width) * EnvGen.ar(e, doneAction: 2))
}).add;
)

Pbind(
	\instrument, Pwrand([\a, \b], [0.1, 0.9], inf),
	\width, Pseq((1..20) / 80, 100),
	\dur, 0.15
).play

BTW: the line

\instrument, [\instA, \instB].choose

causes to choose an instrument when the code is evaluated, maybe you want that, otherwise, to choose per event, you can write it like this (or use Prand)

\instrument, Pfunc { [\instA, \instB].choose }

Thanks for your response dkmayer. With regard to your last point on binding per evaluation or per event. It is the subtleties of such points that make SuperCollier so fascinating. Changing instruments once per event would be overkill. As it is, the Pbind itself is part of a macro loop itself evaluated x 291 over a 13 minutes composition; having the opportunity to select instruments 291 times is more than enough variation! With regard to your first point I’m going to test my setup to see if the DynKlank arguments sent via a pattern are indeed ignored by the hard-coded version.

Hi dkMayer. Re the solution proposed the dynKlank variables are bound with Control.names which once set will populate the array unless overwritten.

Given the following situation:

(
SynthDef(\dynKlank1, {|out, freq|
    var freqs, ringtimes, signal;
    freqs = Control.names([\freqs]).kr([800, 1071, 1153, 1723]);
    ringtimes = Control.names([\ringtimes]).kr([1, 1, 1, 1]);
    signal = DynKlank.ar(`[freqs.poll, nil, ringtimes ], Impulse.ar(2, 0, 0.1));
    Out.ar(out, signal); 
}).add
)

(
SynthDef(\dynKlank2, { |out, freq|
    var freqs, ringtimes, signal;
    freqs = Control.names([\freqs]).kr([0!3]);
    ringtimes = Control.names([\ringtimes]).kr([0!3]);
    signal = DynKlank.ar(`[freqs.poll, nil, ringtimes ], Impulse.ar(2, 0, 0.1));
    Out.ar(out, signal); 
}).add
)


    (
    m = Pbind(
    	\instrument, [\dynKlank1, \dynKlank2].choose,
//__________________________
    	\freqs, [[123, 305, 455]],
//________________________
    	\delta, Pseq([0.5],inf),
    	\freq, 440,
    	\amp, 0.2
    ).play
    )

The print out in the post window gives the following:

Gen Array [0]: 123
UGen Array [1]: 305
UGen Array [2]: 455
UGen Array [3]: 1723
UGen Array [0]: 123
UGen Array [1]: 305
UGen Array [2]: 455
UGen Array [3]: 1723
UGen Array [0]: 123
UGen Array [1]: 305
UGen Array [2]: 455
UGen Array [3]: 1723
UGen Array [0]: 123
UGen Array [1]: 305
UGen Array [2]: 455
UGen Array [3]: 1723
UGen Array [0]: 123
UGen Array [1]: 305
UGen Array [2]: 455
UGen Array [3]: 1723

which clearly shows that the last member of the freqs array in dynKlank1 remains in place as expected whilst the other members of the array are overwrtten. My aim was to use conditional branching in a single Pbind to leave the array of dynKlank1 untouched whilst populating the array of dynKlank2 as per condition. dynKlank2 as can be seen has different array sizes.

In fact my ideal solution would be one where the size of the array could be set externally at will. Something like:

(
SynthDef(\dynKlank, { arg arrSize = 3, out, freq;
    var freqs, ringtimes, signal;
	freqs = Control.names([\freqs]).kr(Array.with(0!arrSize));
    ringtimes = Control.names([\ringtimes]).kr(Array.with(0!arrSize));
    signal = DynKlank.ar(`[freqs.poll, nil, ringtimes ], Impulse.ar(2, 0, 0.1));
    Out.ar(out, signal); 
}).add
)

I’ve tried every which way to make such an approach work but SuperCollider does not recognise arrSize parameter to be an integer - which is curious!

  1. You cannot change the size of the control array after the synthdef is built (and sent to the server). If you need to have multiple array sizes, you have to generate different synthdefs for them. Those can share the same code general structure. There have been various frameworks done for this purpose, e.g. see SynthDef.wrap. But it’s not clear you actually need this here…

  2. What dkmayer suggested was was not having freqs, and ringtimes as controls at all in your dynKlank1, i.e.

(
SynthDef(\dynKlank1, {|out, freq|
    var freqs = #[800, 1071, 1153, 1723];
    var ringtimes = #[1, 1, 1, 1];
    //...

Is there a reason why those are settable as controls in dynKlank1? (I.e. is there some code you haven’t show us that externally sets those in dynKlank1?) Because having two control arrays of different sizes, with the same name, is going to be troublesome down the road. If you accidentally set the 3-element array to a 4-element one, the extra elements are silently discarded.

(SynthDef(\trouble, {
	arg a = #[0, 0], x = 1, y = 2;
	[a, x, y].poll(2);
}).add)

z = Synth(\trouble, [\a, #[1, 3, 5, 7]])

Outputs

UGen Array [0]: 1
UGen Array [0]: 3
UGen Array [1]: 1
UGen Array [2]: 2
  1. Code-style observation: mixing arg-style and Control.name style parameters is a fairly bad idea because it can become hard to keep track of what your synth args/controls actually are.

Ok, I see. If you want to handle that with conditional, you can use a construct like this, group keys in an array:

...
\keyX, 0,

[\rFreqs, \rAmps, \rRingtimes], Pfunc { |e| (e[\instrument] == \instA).if {
			[[[100, 200, 300]], [[0.1, 0.2, 0.5]], [[1, 2, 5]]]
		}{
			[[[100, 200, 300, 400]], [[0.1, 0.2, 0.5, 0.7]], [[1, 2, 5, 3]]]
		}
	},

\keyY, 0,
...

But as you noticed yourself, actually it would be more practical to have one SynthDef with flexible array size. As @RFluff said, doing this by a size argument is impossible in principle as the graph structure of a synthdef is fixed (you cannot change the graph with a control input), however you can do a workaround with a maximum size and zeropadding. Another workaround is a “SynthDef factory” producing SynthDefs for different sizes. As this question and others related with array args and patterns have come up so often in the past I have written a tutorial, included in miSCellaneous_lib quark: “Event patterns and array args”.

2 Likes

Hi dkmayer. Many thanks. What opened up things was the parallel bindings: [\rFreqs, \rAmps, \rRingtimes]. I was not aware of this technique. Problem solved.

It’s probably not a feature you see used often, but it is documented Pbind’s help page with this example:

// standard syntax, arguments alternate symbols and patterns
(
Pbind(
    \instrument,        \test,
    \nharms,             Pseq([4, 10, 40], inf),
    \dur,                Pseq([1, 1, 2, 1]/10, inf),
    #[freq, sustain],    Ptuple([ // assignment to multiple keys
                            Pseq( (1..16) * 50, 4),
                            Pseq([1/10, 0.5, 1, 2], inf)
                        ])
).play;
)

Although that example is rather suboptimal as you really want it when the array’d keys have values generated that depend on each other.