NamedControl: a better way to write SynthDef arguments

I can see your point. But most languages (that i’ve used) designate function args with parenthesis, e.g. Java, Python, JavaScript. I suppose neither the arg keyword nor the double pipes are as clean or familiar as that. Pipes generally have a more functional purpose in most languages (and supercollider) e.g. logic- if (true || false) {}. For me the pipe character used as argument delimiter overloads that meaning. The arg keyword is less terse but its intention is perfectly clear.

did you know that commas and equals signs are optional in the pipe notation?

|x = 3, y = 4|
|x = 3 y = 4|
|x 3, y 4|
|x 3 y 4|

and parentheses are required around the argument if it isn’t a literal?

|freqs = [440, 440]| // syntax error
|freqs = #[440, 440]| // ok
|freqs = ([440, 440])| // ok

|server = Server.default| // syntax error
|server = (Server.default)| // ok

the parentheses are meant to resolve some ambiguous parses. but all the ambiguous cases seem to involve missing commas, leaving me to wonder why commas were ever optional in the first place.

5 Likes

I rather like

|x 3, y 4|

Appeals to my inner Haskell programmer. Probably wouldn’t use it in practice though.

While SuperCollider is a much better language than critics think, parser and API consistency aren’t among its virtues.

Oh and you win Nathan. Named controls - I’m a convert. I just had to write a complicated synthdef and had a moment where I realized that named arguments would make my life way easier.

1 Like

Nathan, as you have already put energy in collecting these arguments, are you planning to add a tutorial in SCDoc ? That would be really helpful – and: as these errors with argument rates in the SynthDef helpfile were discussed recently, I realized that there’s not a single mentioning of NamedControl in the SynthDef help (!), that should definitely be added! IMO it would be ideal to have a paragraph about the existence of NamedControl in SynthDef help plus an additional tutorial.
BTW: IIRC NamedControl was introduced relatively lately which would explain its underrepresentation in the other (often older) help files. I know for sure that array arguments were introduced later on, and these are also underrepresented.

the new “Learn SC” tutorial effort will teach SC using NamedControls, and will probably contain these arguments as a side note of some kind. help files are nice but in practice they don’t draw quite the attention that forum posts or popular tutorials do.

(also, a completely shallow reason: personally i don’t like working with SCDoc.)

It’s good to know that there is an alternative syntax for arguments. However, using the arg keyword has one big advantage: by looking at the beginning of the SynthDef I can easily tell all the arguments and their default values. If I used the NamedControl style, I’d have to look all over the definition for finding the same information.

In most programming languages it’s usually discouraged to have functions with more than 3-4 arguments but sclang is a bit special in this regard: a SynthDef can easily have more than 5. If someone wants to write code which is easy to read for others (or for themselves later down the line) they should pay attention to formatting arguments.

A thought about multichannel controls: can’t the freqs be specified as Array.fill(8, 440) or even [440].stutter(8)?

It’s good to know that there is an alternative syntax for arguments. However, using the arg keyword has one big advantage: by looking at the beginning of the SynthDef I can easily tell all the arguments and their default values. If I used the NamedControl style, I’d have to look all over the definition for finding the same information.

if you want all your arguments at the top, you can easily do so:

SynthDef(\sine, {
    var out = \out.kr(0), freq = \freq.kr(440), amp = \amp.kr(0.1);
    Out.ar(out, SinOsc.ar(freq) * amp);
}).add;

A thought about multichannel controls: can’t the freqs be specified as Array.fill(8, 440) or even [440].stutter(8) ?

try it :slight_smile:

i guarantee you it won’t work, and the reasons are clearly explained in the original post. it has to do with limitations in the way sclang stores default arguments in function metadata — if the default argument is not a literal, it can’t be accessed through the function’s prototype frame.

{ |foo = (Array.fill(8, 440))| }.def.prototypeFrame // -> [ nil ]
{ |foo = ([440].stutter(8))| }.def.prototypeFrame // -> [ nil ]

Yes, but in this case you lose the advantage of less typing – actually it looks a bit more complicated to me. But it all comes down to personal preferences, I think (as most coding style does).

And you’re right, Array.fill() etc. cannot be used as default values for arguments in this case. I found the relevant part in the documentation (at least I think this is it):

In general arguments may be initialized to literals or expressions, but in the case of Function:play or SynthDef:play, they may only be initialized to literals.

Coming from a software developer background with experience mostly in modern high level languages (JavaScript, Python, etc.) sclang often confuses me…

i get your point, but the cool thing is that NamedControls at least give you a choice. when i’m doing some quick sound synthesis stuff, i’m not so concerned about clean and readable synthdefs. interspersing parameters with code allows me to quickly replace any constant value with a parameter, and i’m no longer slowed down by needing to properly maintain that argument list.

for more complex synthdefs or code meant for sharing with other people, yes, it’s better to summarize parameters at the top, and this puts NamedControls are at a verbosity disadvantage to argument lists. either way, i think this is greatly outweighed by the other glaring issues with argument style.

yeah, believe me, i’m fully aware of how annoying sclang can be. the lexer/parser/compiler is probably the oldest surviving part of the platform, and the most difficult for contributors to do any kind of maintenance or changes on — huge messy codebase with zero documentation. a makeover for the language is on the horizon though.

nevertheless i do hold that sclang is a relatively nice language if you can avoid the traps. some popular tutorials (w/ all due respect to their luminous authors) encourage some sloppy habits and can give bad first impressions to seasoned programmers. the new tutorial effort will hopefully remedy that.

The issues with creating synthdefs aren’t really a language thing. SynthDefs are better thought of as DSLs for describing how a particular synth should be constructed. The issues with parameters are flaws with the synth construction ‘library’. My opinion is that given what we know now (after 20 years of DSL knowledge), but didn’t know then, that was probably not the best way of doing things - but hey, it still works remarkably well and is a stunningly successful achievement.

While I think SCLang does have it’s flaws and idiosyncrasies, I don’t know that these are (other than a couple of glaring examples) particularly worse than most other mainstream languages. Compared to JavaScript SCLang looks pretty good (SCLang just doesn’t have a ‘SCLang the Good Bits’ tutorial). And for musical purposes SCLang has a number of features which are really good and are probably unmatched by any mainstream language that I know of (e.g. JavaScript has the flexibility offered by events/environments in SuperCollider, but lacks the flexibility of routines and easy task scheduling).

I’m really interested in that. What I’d like to see is some kind of API which makes it easy to create different kind of frontends for scsynth – either in another language or a visual environment, maybe something like Audulus or Reaktor.

Hi Nathan,

Thanks a lot for these new insights! I am struggling a bit with implementing an array of controls for my Out buses:

SynthDef(\soundin, {
        var sig, env, fx, fxlvl, numFx;
        numFx = \numFx.ir(4);
        env = EnvGen.kr(Env.asr(0.01, 1.0, 0.01), \gate.kr(0), doneAction: \da.kr(2));
        sig = SoundIn.ar(\in.kr(0), mul: \lvlin.kr(0.2));
        sig = sig * env * \lvlout.kr(0.2);
        sig = PanAz.ar(4, sig, \pan.kr(0), width: \width.kr(2.0));
        Out.ar(\out.kr(0), sig);
        Out.ar(\fxout.kr(0 ! numFx), sig * \fxlvl.kr(0 ! numFx));
    }).add;

This doesn’t work, unfortunately… I would like to be able to do something like this:

x = Synth(\soundin, [\fxout, [4, 8, 12, 16], \fxlvl, [0, 0.2, 0.1, 0.4]]);
x.set(\fxlvl[0], 1);

… and so on…

Any help on this would be greatly appreciated!

Best,
Kenneth

this is a limitation not specific to namedcontrols, but a general problem with SynthDefs. the UGen graph is fixed at compile time, so you can’t e.g. change the size of an array of UGens in response to a parameter.

the typical workaround is to programmatically define a family of SynthDefs.

(
(1..8).do { |numFx|
    SynthDef(\soundin ++ numFx.asString, {
        var sig, env, fx, fxlvl;
        env = EnvGen.kr(Env.asr(0.01, 1.0, 0.01), \gate.kr(0), doneAction: \da.kr(2));
        sig = SoundIn.ar(\in.kr(0), mul: \lvlin.kr(0.2));
        sig = sig * env * \lvlout.kr(0.2);
        sig = PanAz.ar(4, sig, \pan.kr(0), width: \width.kr(2.0));
        Out.ar(\out.kr(0), sig);
        Out.ar(\fxout.kr(0 ! numFx), sig * \fxlvl.kr(0 ! numFx));
    }).add;
};
)

there is an important caveat to be aware of with the Out ugen that i am also noticing here. the second argument to Out does not multichannel expand like a normal UGen. Out.ar(0, [foo, bar, baz]) does a special Out-specific trick where foo is outputted to bus 0, bar to bus 1, and baz to bus 2.

i think something like this would work.

(
(1..8).do { |numFx|
    SynthDef(\soundin ++ numFx.asString, {
        var sig, env, fxlvl, fxout;
        env = EnvGen.kr(Env.asr(0.01, 1.0, 0.01), \gate.kr(0), doneAction: \da.kr(2));
        sig = SoundIn.ar(\in.kr(0), mul: \lvlin.kr(0.2));
        sig = sig * env * \lvlout.kr(0.2);
        sig = PanAz.ar(4, sig, \pan.kr(0), width: \width.kr(2.0));
        Out.ar(\out.kr(0), sig);
        fxlvl = \fxlvl.kr(0 ! numFx);
        fxout = \fxout.kr(0 ! numFx);
        Out.ar(fxout, sig *.t fxlvl);
    }).add;
};
)

personally, in this case, i would separate the fx gains into “send” Synths that simply bus audio from one place to another with controllable gain. i use this SynthDef for routing different tracks to aux busses:

SynthDef(\send, {
    var snd;
    snd = In.ar(\in.kr(0), 2);
    snd = snd * \amp.kr(1);
    Out.ar(\out.kr(0), snd);
}).add;

i’m terribly sorry, kind of in a hurry and can’t really explain this better, but hope this helps at least a little.

1 Like

Hi again,

Only saw this now. Looks like an interesting approach! Will chew on it when I have a bit more time on my hands to see how this would fit into my overall setup…

Started using this as soon as I saw this post. Definitely a nicer way to plug controls into the SynthDef. I’m sold! Thanks!

Hi there -
This is an amazing resource - thank you for posting it.
I did want to ask a question, though, since I am currently a few months into a project with many NamedControls.
I’m finding that I have many of the same arguments, ultimately, for each Synth… and they’re all getting numbered in order to keep up with all of the controls. So, for instance, a cutoff frequency argument on different filters is becoming \cutoff1.kr, \cutoff2.kr, \cutoff3.kr…

Are there ways to make NamedControl more linked to the Synth instance directly?

hi, glad to hear you’re enjoying NamedControls.

i’m not 100% sure what you mean — do you mean “SynthDef” rather than “Synth”? if that’s the case, you can freely reuse the same NamedControl across different SynthDefs. they won’t interfere. if i guessed wrong, maybe a short code example would help clarify.

A SynthDef is an abstraction, you are going to mix the levels of abstract definition (SynthDef) and concrete instantiation (Synths), where the differentiation should be done.

To be more concrete, you could arrange your data like this:

// containers for data

~synths = […, …, …, … ]

~cutoff = […, …, …, … ]

then actions can be defined like this (and easily itereated over all indices)

~synths[0].set(\cutoff, ~cutoff[0])

// let n be the number of synths

n.do { |i| ~synths[i].set(\cutoff, ~cutoff[i]) }

Greetings

Daniel

Thank you! I got it now.