Overriding arguments of SynthDef variant in Pbind

Hi all.

I’m building a large subtractive synth in one SynthDef that I can use for many different uses. I want to implement presets for this synth, and recently discovered the ‘variants’ option of SynthDef.new. It’s really powerful, and a clean way to code presets in my opinion.

I can use these variants in a Pdef/Pbind, but if I do this I cannot then use the Pbind arguments to change argument values further. I would want to have this control in a live performance to tweak aspects of one of my presets on-the-fly, for example. See code example below - if I set an argument in ‘variants’ and use that variant in a Pbind, any further changes later in the Pbind seem to be ignored.

(
SynthDef.new(\testsynth, {
	// lpf is set to 6000 by default
	arg freq = 440, amp = 0.5, out = 0, pan = 0, gate = 1, lpf = 6000;
	var sig, env;
	
	env = Env.perc().kr(2);
	sig = WhiteNoise.ar(amp);
	
	sig = sig * env;
	sig = LPF.ar(sig, lpf);
	
	Out.ar(out, Pan2.ar(sig, pan));
},
// lpf is set to 500 by the variant, if applied
variants: (testvariant: [lpf: 500])
).add;

Pdef(\test, Pbind(
	\instrument, 'testsynth.testvariant',
	// if variant is used, argument below is ignored
	\lpf, 5000,
));
)

Am I just using Pbind and variants incorrectly?

This appears to be poorly documented (perhaps my fault) but it should be:

Pdef(\test, Pbind(
	\instrument, \testsynth,
	\variant, \testvariant,
	// if variant is used, argument below is ignored
	\lpf, 5000,
));

This is mentioned AFAICS only in Pbind help, and only briefly. :no_mouth: (But, Pmono is an exception – in Pmono, it should be `\instrument, ‘testsynth.testvariant’ – rough edge in the interface.)

The reason is: Written as a single \instrument, every event would have to parse the string. That’s slower than providing them as separate strings/symbols to begin with.

hjh

1 Like

Thanks for your reply!

When I try your code, it seems the variant isn’t loaded, but the \lpf argument does have an effect again. I did see the variant argument in the Pbind docs so I tried this argument pair before. Very strange!

Did you ever solve this? I have the same issue, of not being able to override the variant’s parameters in a Pbind.

With the syntax Pbind(\instrument, ..., \variant, ...) it also plays the default synthdef and ignores the variant.

@jamshark70
I’m also running in this problem running patterns with synthdef variants. I’m a bit confused by the implementation… could you perhaps help clarifying how it is supposed to work, or if there is a bug?

tl;dr: using variants in Pbinds require setting the synthDesc key, but even then, the generated events override variants’ settings for keys with default values like freq.

Longer break-down:

  1. variant is used in serverEvent: however, ~synthDefName (Event.sc: line 295) expects the event to have ~synthDesc defined, and I can’t find any other way to do it than to explicitly define it in my events (e.g. in Pbind). It looks like something that should happen automatically, isn’t it?
Pbind(
    \instrument, \test, \variant, \var, 
    \synthDesc, SynthDescLib.global.at(\test)
)
  1. All the above does is generating a message with the correct defName.variant syntax. This is exactly the same result as setting instrument, 'test.var'. So, I suspect this is not true (anymore?):

It looks like it’s quite the opposite: providing variant requires an extra SynthDescLib lookup, to arrive at the same result.

  1. Default values are set anyway, regardless of variant, possibly defeating the purpose of variants. Here is an example bundle generated by the above code:
[ [ 9, test.var, 1733, 0, 1, out, 0, amp, 0.1, freq, 261.6255653006 ] ]

If the variant var is setting a frequency, it will be discarded in favor of the default value. Any other key is perfectly overridable as well, which I think it’s ok. But shouldn’t we check the variant definition before setting default values? Alternatively we should make it clear that the variant systems doesn’t work with patterns for keys like \freq, \amp and \out.

I guess the core of the problem is that SynthDesc doesn’t store variants settings, so there’s no way for Event to access that info… but if it did, we would get default values for all SynthDef controls, and then we would need to decide if a variant is setting all parameters, or only the ones that differ from non-variant defaults…

Proposals

  1. Review/automatize the ~synthDesc mechanism?
  2. I think the most viable solution is to update docs to state clearly:
  • how variants can be used with patterns (w. example)
  • how patterns can override any variant-defined default value
  • how this applies specifically to keys like freq and amp

Alternatively:

  • update Event to avoid setting defaults when a variant is used?

Aha… compare my commit against a later one:

Mine:

~synthDesc = desc = synthLib.at(instrument);

Later:

desc = synthLib.at(instrument);

But actually even at this later point, someone else had already converted ~synthDesc to synthDesc (a local var).

Actually I knew what I was doing when I assigned it to the environment var! The whole purpose was to set it in the Event so that synthDefName would have access to it.

So the later changes to this line need to be reverted.

No – event lookup is logarithmic-time at worst (but usually faster). String parsing is linear-time = slower. You do not want to parse a string for every event.

(
var str = "myInstrument.myVariant";

bench {
	1000000.do {
		var i = str.indexOf($.);
		var d = str[..i-1];
		var v = str[..i+1];
		0
	}
}
)

time to run: 0.70456394500002 seconds.
-> 0.70456394500002

(
var d = \myInstrument;
var v = \myVariant;

bench {
	1000000.do {
		var lib = SynthDescLib.global;
		lib[d];
		lib[d];
	}
}
)

time to run: 0.23571666600003 seconds.
-> 0.23571666600003

Event values, including event-prototype defaults, have always taken precedence over synthdef defaults.

It would be fair to write something, maybe even a FilterPattern, to load variant values into the event before playing the event.

The default event prototype is already (excessively) complex – I’d favor solutions that don’t change the rules here, but instead provide more useful information to the default event prototype.

hjh

Of course! My point was that string parsing is never performed in Event code anyways :slight_smile: Or am I missing it?
EDIT: to make it clearer, I mean that an \instrument string like “defName.variant” is never split by Event to get “variant”. The \instrument string is directly passed to the server as it is. On the other hand, if \variant is provided, there is a string concatenation in ~synthDefName ("%.%".format(instrument, variant)). Furthermore, as it is, Event doesn’t need to know if there is a variant or not, since it’s anyways allowed to override any of the variant-defaults.

Agree!

Sure… but to look up the SynthDesc, it would need to split the string.

Or… SynthDescLib could store a SynthDesc once per variant, eliminating the need for a separate key or for string handling.

Thinking further – what is really needed is a preset system. Variants sort of half heartedly try to be this, but in practice, they’re not. My (perhaps biased) opinion is that the issues between SynthDef variants and events demonstrate what happens when you (in this case, JMc) try to glom a feature onto SynthDef instead of designing the feature with a proper object model: sooner or later you hit a barrier.

I’m not sure that variants actually worked out all that well in the end.

hjh

Oh yeah, you’re absolutely right, sorry for the slow steps here :slight_smile:

Actually I agree with you. Very superficially, my point of view is that SynthDef variants are presets that get stored on the server… but I see presets as a language feature (it’s the language that should set control values). I mean, maybe we took default control values too far.

Maybe it is a good idea to discourage using SynthDef variants with patterns, and perhaps encourage using Event parentTypes instead? Or what would be a recommended option to replace SynthDef variants in the Pattern world?

And am I going OT hijacking this thread too much?

Hi all,

I ran into this same problem today and decided to search the forum for advice, so here I am reviving this old thread :slightly_smiling_face:

I came up with a workaround inspired by @elgiano’s suggestion to use Event parentTypes, so I thought I’d share my code in hopes that it may perhaps be useful to someone. This is not a robust preset system, just a minimum viable example, but feedback is welcome:

(
SynthDef(\test, {
    var sig = SinOsc.ar(\freq.kr(440));
    var env = Env.perc.ar(2);
    sig = sig * env * \amp.kr(0.3);
    sig = Pan2.ar(sig, \pan.kr(0));
    Out.ar(0, sig);
},
// variants are not stored in SynthDesc, so instead...
// store presets in metadata, which is stored in SynthDesc:
metadata: (
    presets: (
        lowleft: (freq: 100, pan: -1),
        hiright: (freq: 900, pan: 1)
    )
)).add;
)

// add an eventType which assigns the selected preset to be the proto event:
(
Event.addEventType(\preset, {
    currentEnvironment.proto = SynthDescLib.at(~instrument).metadata.presets[~preset];
    ~type = \note;
    currentEnvironment.play;
});
)

// test it out:
(
Pdef(\testpat, Pbind(*[
    type: \preset,
    instrument: \test,
    preset: Pseq([\lowleft, \hiright, `nil], inf),
    // uncomment below to override presets
    // pan: 0, 
    // freq: 600
])).play;
)

I agree that a more robust preset system is needed, or at least allow for variants to be stored in the SynthDesc.

3 Likes