Copying some stuff from web

// variable snd
// operator *
// integer literal -20
// method call dbamp
snd = snd * -20.dbamp;

// variable snd
// operator *- (this doesn't exist)
// integer literal 20
// method call dbamp
snd = snd*-20.dbamp;

Removing the spaces inadvertently changed the way it parses.

Another opinion: Many users, particularly new users, I guess wish to save some typing by deleting as much white space as possible. I find that it hurts readability to have a large mass of undifferentiated characters (I strongly dislike reading such code) – and as you found here, spaces are sometimes syntactically significant.

hjh

The shortcut is also fine. It is documented and its usage is sufficiently widespread. The alternatives are to write out NamedControl.kr(\freq, 440), which is absurdly verbose for the amount SynthDef arguments are used, or to use the argument style, which has serious problems when it comes to specification of rate.

Again, agree to disagree. It’s documented, but, “I have no idea what that’s referring to.” I guess the solution to that is an RFC to change the synth control style throughout the help system.

I did this with my live coding system loaded (actually missing one file, but close enough):

(
a = (control: 0, arrayed: 0, audio: 0, scalar: 0, total: 0);

SynthDescLib.global.synthDescs.do { |desc|
	if(desc.name.asString.beginsWith("system_").not) {
		desc.controls.do { |ctl|
			var rate = ctl.rate;
			if(ctl.name != '?') {
				a[rate] = (a[rate] ?? { 0 }) + 1;
				if(ctl.defaultValue.size > 1) {
					a[\arrayed] = a[\arrayed] + 1;
				};
				a[\total] = a[\total] + 1;
			};
		};
	};
};

a
)

-> ( 'scalar': 22, 'arrayed': 12, 'control': 1443, 'audio': 0,
  'total': 1465 )

Arrayed controls clock in at 12/1465 < 1%, and scalar rate at 22/1465 ~= 1.5% scalar. It’s hard to count tr controls this way but I don’t use them frequently. Perhaps that’s why I don’t mind to write out NamedControl in the 0.8% cases of arrayed controls (where there’s really no good other way) or the couple percent other cases.

For the majority of cases, it’s a nice optimization that function args collapse into one Control unit.

(
SynthDef(\oneControl, {
	var ctl = Control.names(Array.fill(250, { |i| ("ctl" ++ i).asSymbol }))
	.kr((1..500));
	Out.kr(0, ctl[0]);
}).add;

SynthDef(\namedControls, {
	var ctl = Array.fill(250, { |i|
		NamedControl.kr(("ctl" ++ i).asSymbol, i+1)
	});
	Out.kr(0, ctl[0]);
}).add;
)

// 10-11% after setting CPU performance mode
a = (instrument: \oneControl, ctl0: (0..100), type: \on).play;

a.put(\type, \off).play;

// 18-19%
a = (instrument: \namedControls, ctl0: (0..100), type: \on).play;

a.put(\type, \off).play;

hjh

If efficiency is a concern, ir controls are significantly better (these require NamedControl). The peak cpu of this is 0.5%, where your other two examples peaked at 18% and 42% for me:

SynthDef(\namedControlsIr, {
	var ctl = \ctl.ir((0..100));
	Out.kr(0, ctl.sum.poll);
}).add;

Somewhat unrelated but - for what it’s worth, I’m not totally sure what you’re doing with sending an array to a non-array control is valid? Setting a single control, ctl0, with an array works only because the server blindly writes all the data from that array into memory without looking at where it’s writing. It happens to be the case that the synth args are ordered in memory like ctl0, ctl1, ctl2, … etc, but I DON’T think this is guaranteed to be the case - probably this would be considered undefined behavior that just happens to work right now.

Overwriting the bounds of controls like this can cause subtle and very nasty bugs (and IIRC crashes as well) so to be honest scsynth and supernova should probably have checks in place to make sure this doesn’t occur.

That’s not an apples-to-apples comparison.

I’m using the DSP CPU testing methodology of creating the same number of a lot of something, and measuring CPU for those.

  • My test 1: 250 independently-named single-value controls, created in a single Control unit, x 101 synths = 101 Control units, 25250 active controls.

  • My test 2: 250 independently-named single-value controls, each with its own dedicated Control unit, x 101 synths = 25250 Control units, 25250 active controls.

… and found from this test that the overhead of entering and exiting 25149 additional Control_next() functions is not exactly trivial.

To compare ir, you would need two tests: test 1 architecture, and test 2 architecture, just changing the rates.

(Actually, as I was doing that, I found a mistake in the oneControl synth – it should be .kr((1..250)) – fixing this mistake reduces CPU for test 1 to about 6%! So the margin is even larger than I thought at first.)

  • kr
    • Test 1 (fixed) = 6%
    • Test 2 = 18%
  • ir
    • Test 1 = 0.2% (probably within margin of error?)
    • Test 2 = 0.2%

I can’t find the bit in the code that’s responsible for this, but I’d have to guess that scalar-rate controls simply don’t call the nextfunc at all. In that case, it wouldn’t matter how many there are.

But “make all your controls ir” isn’t really a solution, is it? :laughing: I like bus mapping…

Not true – arg i_out is used in many places in the class library. Also the construction in my test 1 is valid to create ir controls as well. (IIRC Control.names(array_of_symbols).kr(array_of_defaults) is how the function’s argument list is converted into Control channels – it is a valid usage, though not common, and not particularly user-friendly, which is why NamedControl was introduced.)

I’m not setting a non-array control to an array.

s.dumpOSC(1);

a = (instrument: \oneControl, ctl0: (0..100), type: \on).play;

[ "#bundle", 16602913446784111450, 
  [ 9, "oneControl", 1404, 0, 1, "ctl0", 0 ],
  [ 9, "oneControl", 1405, 0, 1, "ctl0", 1 ],
  [ 9, "oneControl", 1406, 0, 1, "ctl0", 2 ],
  [ 9, "oneControl", 1407, 0, 1, "ctl0", 3 ],
  [ 9, "oneControl", 1408, 0, 1, "ctl0", 4 ],
  [ 9, "oneControl", 1409, 0, 1, "ctl0", 5 ],
  [ 9, "oneControl", 1410, 0, 1, "ctl0", 6 ],
  ....
]

hjh

Bus mapping works with ir, it just pulls the value once when the synth is created.

Yup, mine was a typo - but as you said the difference is still roughly the same (e.g. extremely low CPU usage for the ir case).

Got it, I misread your code - it would take another [] to do what I was thinking.

James, would you mind explaining what is actually happening here versus with the NamedControl?

Is it that an array inside of NamedControl ends up returning a Control unit per each element within an array, versus when using an array inside of an argument, Control encapsulates the entire array within one Control unit?

I use NamedControls everywhere, and really like them. I even tend to declare most of them toward the top, where I’d normally use args. One of the subtle things I appreciate is the IDE’s syntax highlighter doesn’t recognize args as such, but symbols pop right out for me.

But I also use a lot of arrays within them. And like making efficiency mods. The CPU difference is striking here!

Boris

Perhaps compare:

SynthDef(ugenGraphFunc: {
	arg bus = 0, freq = 440, phase = 0, amp = 0.1;
	Out.ar(bus, SinOsc.ar(freq, phase) * amp)
}).children.select({ arg each; each.class == Control}).size == 1

and:

SynthDef(ugenGraphFunc: {
	Out.ar('bus'.kr, SinOsc.ar('freq'.kr, 'phase'.kr) * 'amp'.kr)
}).children.select({ arg each; each.class == Control }).size == 4

There’s no particular reason the graph builder couldn’t coalesce the second case, but for the moment it doesn’t.

Interesting. Thanks for this example.

I tried this:

(
SynthDef(ugenGraphFunc: {
	var bus = Control.names(\bus).kr(0),
	freq = Control.names(\freq).kr(440),
	phase = Control.names(\phase).kr(0),
	amp = Control.names(\amp).kr(0.1);
	
	Out.ar(bus, SinOsc.ar(freq, phase) * amp)
}).children.select({ arg each; each.class == Control}).size == 4
)

So it seems like it’s the naming that’s preventing them from being bundled? Which is odd, because it seems like in Control’s source code, they’re being associated with a name, anyway…?

Another thing that I noticed:

(
SynthDef(ugenGraphFunc: {
	Out.ar('bus'.kr, SinOsc.ar('freq'.kr(440), 'phase'.kr) * 'amp'.kr(0.1))
}).children
)
// -> [ a Control, a Control, a Control, a SinOsc, a Control, a BinaryOpUGen, an Out ]

(
SynthDef(ugenGraphFunc: {
	Out.ar('bus'.kr, SinOsc.ar('freq'.kr(440), 'phase'.kr) * 'amp'.kr(0.1))
}).children[0].values
)
// -> [ 0.0, 440, 0.0, 0.1 ]

(
SynthDef(ugenGraphFunc: {
	Out.ar('bus'.kr, SinOsc.ar('freq'.kr(440), 'phase'.kr) * 'amp'.kr(0.1))
}).children[1].values
)
// -> [ 440 ]

So it seems like the first Control bundles them up anyway?

The graph description stores “parameter” meta-data as well as the Control unit generators, see:

  • int32 - number of parameters (P)
  • [float32] * P - initial parameter values
  • int32 - number of parameter names (N)
  • [ param-name ] * N

at https://doc.sccode.org/Reference/Synth-Definition-File-Format.html

Parameters can be referred to by both name and index.

If you write all of the parameters at once, for instance as pseudo-arguments, the indices can be derived from the sequence of the names.

If you write the parameters distinctly, and want to refer to them by index, then you need to know what the rule is for assigning the indexes!

SynthDef(ugenGraphFunc: {
	arg bus = 0, freq = 440, phase = 0, amp = 0.1;
	Out.ar(bus, SinOsc.ar(freq, phase) * amp)
}).allControlNames.collect({ arg each; each.index -> each.name })
==
SynthDef(ugenGraphFunc: {
	Out.ar('bus'.kr, SinOsc.ar('freq'.kr, 'phase'.kr) * 'amp'.kr)
}).allControlNames.collect({ arg each; each.index -> each.name })

There are various ways to write graph constructors, each with nice and less nice aspects.

First, it would be helpful to understand the purpose of the test.

I wanted to demonstrate the performance difference between a single Control unit with a large number of channels, and a large number of single-channel Controls.

Note at this point that there is no need to consider arrayed arguments for this. In fact, it would be bad to use arrayed arguments here. A good test eliminates unnecessary variation in the cases. If I want to measure the above-stated performance difference, then I want everything to be the same except for the Control structure: same number of controls, same names etc. So I do not want non-arrayed Control vs arrayed NamedControl, or vice versa.

I could have written:

(
d = SynthDef(\oneControl, {
	arg ctl0 = 1, ctl1 = 2, ctl2 = 3, ctl3 = 4, ctl4 = 5,
	ctl5 = 6, ctl6 = 7, ctl7 = 8, ctl8 = 9, ctl9 = 10,
	ctl10 = 11, ctl11 = 12, ctl12 = 13, ctl13 = 14, ctl14 = 15,
	ctl15 = 16, ctl16 = 17, ctl17 = 18, ctl18 = 19, ctl19 = 20,
	ctl20 = 21, ctl21 = 22, ctl22 = 23, ctl23 = 24, ctl24 = 25,
	ctl25 = 26, ctl26 = 27, ctl27 = 28, ctl28 = 29, ctl29 = 30,
	ctl30 = 31, ctl31 = 32, ctl32 = 33, ctl33 = 34, ctl34 = 35,
	ctl35 = 36, ctl36 = 37, ctl37 = 38, ctl38 = 39, ctl39 = 40,
	ctl40 = 41, ctl41 = 42, ctl42 = 43, ctl43 = 44, ctl44 = 45,
	ctl45 = 46, ctl46 = 47, ctl47 = 48, ctl48 = 49, ctl49 = 50,
	ctl50 = 51, ctl51 = 52, ctl52 = 53, ctl53 = 54, ctl54 = 55,
	ctl55 = 56, ctl56 = 57, ctl57 = 58, ctl58 = 59, ctl59 = 60,
	ctl60 = 61, ctl61 = 62, ctl62 = 63, ctl63 = 64, ctl64 = 65,
	ctl65 = 66, ctl66 = 67, ctl67 = 68, ctl68 = 69, ctl69 = 70,
	ctl70 = 71, ctl71 = 72, ctl72 = 73, ctl73 = 74, ctl74 = 75,
	ctl75 = 76, ctl76 = 77, ctl77 = 78, ctl78 = 79, ctl79 = 80,
	ctl80 = 81, ctl81 = 82, ctl82 = 83, ctl83 = 84, ctl84 = 85,
	ctl85 = 86, ctl86 = 87, ctl87 = 88, ctl88 = 89, ctl89 = 90,
	ctl90 = 91, ctl91 = 92, ctl92 = 93, ctl93 = 94, ctl94 = 95,
	ctl95 = 96, ctl96 = 97, ctl97 = 98, ctl98 = 99, ctl99 = 100,
	ctl100 = 101, ctl101 = 102, ctl102 = 103, ctl103 = 104, ctl104 = 105,
	ctl105 = 106, ctl106 = 107, ctl107 = 108, ctl108 = 109, ctl109 = 110,
	ctl110 = 111, ctl111 = 112, ctl112 = 113, ctl113 = 114, ctl114 = 115,
	ctl115 = 116, ctl116 = 117, ctl117 = 118, ctl118 = 119, ctl119 = 120,
	ctl120 = 121, ctl121 = 122, ctl122 = 123, ctl123 = 124, ctl124 = 125,
	ctl125 = 126, ctl126 = 127, ctl127 = 128, ctl128 = 129, ctl129 = 130,
	ctl130 = 131, ctl131 = 132, ctl132 = 133, ctl133 = 134, ctl134 = 135,
	ctl135 = 136, ctl136 = 137, ctl137 = 138, ctl138 = 139, ctl139 = 140,
	ctl140 = 141, ctl141 = 142, ctl142 = 143, ctl143 = 144, ctl144 = 145,
	ctl145 = 146, ctl146 = 147, ctl147 = 148, ctl148 = 149, ctl149 = 150,
	ctl150 = 151, ctl151 = 152, ctl152 = 153, ctl153 = 154, ctl154 = 155,
	ctl155 = 156, ctl156 = 157, ctl157 = 158, ctl158 = 159, ctl159 = 160,
	ctl160 = 161, ctl161 = 162, ctl162 = 163, ctl163 = 164, ctl164 = 165,
	ctl165 = 166, ctl166 = 167, ctl167 = 168, ctl168 = 169, ctl169 = 170,
	ctl170 = 171, ctl171 = 172, ctl172 = 173, ctl173 = 174, ctl174 = 175,
	ctl175 = 176, ctl176 = 177, ctl177 = 178, ctl178 = 179, ctl179 = 180,
	ctl180 = 181, ctl181 = 182, ctl182 = 183, ctl183 = 184, ctl184 = 185,
	ctl185 = 186, ctl186 = 187, ctl187 = 188, ctl188 = 189, ctl189 = 190,
	ctl190 = 191, ctl191 = 192, ctl192 = 193, ctl193 = 194, ctl194 = 195,
	ctl195 = 196, ctl196 = 197, ctl197 = 198, ctl198 = 199, ctl199 = 200,
	ctl200 = 201, ctl201 = 202, ctl202 = 203, ctl203 = 204, ctl204 = 205,
	ctl205 = 206, ctl206 = 207, ctl207 = 208, ctl208 = 209, ctl209 = 210,
	ctl210 = 211, ctl211 = 212, ctl212 = 213, ctl213 = 214, ctl214 = 215,
	ctl215 = 216, ctl216 = 217, ctl217 = 218, ctl218 = 219, ctl219 = 220,
	ctl220 = 221, ctl221 = 222, ctl222 = 223, ctl223 = 224, ctl224 = 225,
	ctl225 = 226, ctl226 = 227, ctl227 = 228, ctl228 = 229, ctl229 = 230,
	ctl230 = 231, ctl231 = 232, ctl232 = 233, ctl233 = 234, ctl234 = 235,
	ctl235 = 236, ctl236 = 237, ctl237 = 238, ctl238 = 239, ctl239 = 240,
	ctl240 = 241, ctl241 = 242, ctl242 = 243, ctl243 = 244, ctl244 = 245,
	ctl245 = 246, ctl246 = 247, ctl247 = 248, ctl248 = 249, ctl249 = 250;
	Out.kr(0, ctl0);
}).add;
)

But this is boring and noisy. So instead I wrote:

(
e = SynthDef(\oneControl, {
	var ctl = Control.names(Array.fill(250, { |i| ("ctl" ++ i).asSymbol }))
	.kr((1..250));  // fixed the earlier mistake
	Out.kr(0, ctl[0]);
}).add;
)

… which is exactly the same. We can prove that:

d.asBytes == e.asBytes
-> true

This demonstrates that function arguments in a SynthDef will be collapsed down to the smallest number of Control UGens. That’s all.

For \namedControls, I could have written:

(
SynthDef(\namedControls, {
	var ctl0 = \ctl0.kr(1);
	var ctl1 = \ctl1.kr(2);
	var ctl2 = \ctl2.kr(3);
	var ctl3 = \ctl3.kr(4);
	var ctl4 = \ctl4.kr(5);
	var ctl5 = \ctl5.kr(6);
	var ctl6 = \ctl6.kr(7);
	var ctl7 = \ctl7.kr(8);
	var ctl8 = \ctl8.kr(9);
	var ctl9 = \ctl9.kr(10);
	
	// ... OK, here I won't be 250-lines level of pedantic
	// ... snipped
	
	var ctl245 = \ctl245.kr(246);
	var ctl246 = \ctl246.kr(247);
	var ctl247 = \ctl247.kr(248);
	var ctl248 = \ctl248.kr(249);
	var ctl249 = \ctl249.kr(250);
	
	Out.kr(0, ctl0);
}).add;
)

Instead, I constructed the identical NamedControl structure in a loop.

The coding style in that test was simply because, why should I post 300+ lines of repetitive declarations, when I can do it in about 15 lines?

Anyway, NamedControl produces one Control UGen per NamedControl object. An arrayed control has one name, one NamedControl, and multiple values. So, no, the array will not be split out into multiple Controls.

I think this is a bug actually.

Let’s add one more control:

(
d = SynthDef(\test, {
	var extraCtl = \extraCtl.kr(10000);
	Out.ar('bus'.kr, SinOsc.ar('freq'.kr(440), 'phase'.kr) * 'amp'.kr(0.1))
}).children[0].values
)

-> [ 10000, 0.0, 440, 0.0 ]

Oopsy, five controls were declared but this array has only four values.

  1. That means this array is not reliable for anything (i.e. “the first Control bundles them up anyway?” = no).
  2. When an array stops at size 4, usually that means something is .add-ing to it without reassigning the result back into the variable. I can’t find where in the code this is happening, and I don’t know why this is being done only for the first Control to be created. It does look a bit like unintentional behavior, though.

I suspect this is likely to be trickier than one might expect.

Because Control outputs will be used in many, many other units’ inputs, you would need to take each of these OutputProxy objects and mutate them so that they belong to a single Control. (It’s too risky to try to make new OutputProxies and substitute them in other units’ inputs.) I suppose it’s possible, but mutating the graph after the fact… I would expect pitfalls. Probably all of the problems could be solved though.

Note that the function-argument “collapse to one” happens before the function is evaluated, so in that case, there is no need to mutate anything afterward.

hjh

And the performance differences are substantial!

In my post above, when I explicitly called Control to create arguments, instead of using function-arguments, the results are similar to using NamedControls: They did not collapse down into a single Control unit. In your example, this worked out a bit differently.

These all did collapse down into one -

Actually, I’m a bit surprised by this…

Is there anything preventing from it being possible to allow for this behavior for NamedControls?

Looking through the source code, it seems like Control.names can/does process names as an array, thus is able to collapse them down into one, like your earlier example.

NamedControl has a name method instead, which works with only a single entity.

If you do SinOsc.kr 4 times, you would expect 4 SinOsc units in the SynthDef.

It’s actually the same for Control: you have an example which called kr on Control 4 times, and you got 4 Control units. My example calls Control...stuff...kr once, and it produces one Control unit.

Control itself does nothing to collapse them. It allows you to prepare the names and values in a way that can handle multiple differently-named controls within one Control unit. But it is up to you to prepare it that way.

For function arguments, the preparation happens before even calling into the function.

In my opinion, it may be solvable, but difficult.

Understand the process first. The SynthDef builder reads the function arguments and groups them by rate and lag, clumping similar inputs together. Then it makes a UGen for each group, and parcels out these units’ OutputProxy channels into the function’s argument list. (Inside the function, every argument will receive either an OutputProxy or an array of OutputProxies.)

The key here is that these Control / AudioControl / TrigControl / LagControl objects are constructed before function evaluation, and these objects do not change during function evaluation.

To collapse NamedControls – this would be working with objects that are created during function evaluation, not before. So there is no way to prepare static objects in advance. There are only two ways to fold the second NamedControl into the first:

  • At the moment of creating the second one, go back and find the first, and mutate its data structure to contain two channels with different names. (Then the third would do this again, and so on.)
  • Or, during SynthDef post-processing, analyze the NamedControl collection and replace the individual units with a smaller number of units. (IMO this may be riskier – it would touch not only the Controls but perhaps most other units’ index etc.)

Mutating UGens isn’t impossible – the math operator optimizations do this, on a small scale – but it is a bit delicate and the code would need to be tested carefully. Every time a UGen’s internals are modified after it’s already integrated into the graph is another source of risk.

Note that SynthDef.wrap also creates additional Control units, instead of folding the new ones in with the old ones. That’s James McCartney’s code, and he didn’t want to tackle the problem of after-the-fact UGen manipulation. This is probably why NamedControl just says “yikes, that’s too hard (and besides, it’s not 1996 anymore, we don’t have to worry about CPU so much)” and goes with the less efficient option.

It would be nice if someone tackled this. It won’t be me, though (since I’m mainly satisfied with function arguments).

hjh

I wanted to come back to this thread to apologize for my tone. I took James’ comment personally because I consider Symbol:kr etc. to be valid and such criticism of my code to be unfair. Still, that was no excuse to fly off the handle and leave some comments that I really shouldn’t have.

There is already too much macho posturing in other music tech Internet communities and I regret bringing that here. I’m sorry to this community for my behavior, and particularly to @jamshark70.

3 Likes

I want to express my appreciation, Nathan. This is an excellent example of how we all can watch our tone and be better. Thanks.

/*
Josh Parmenter
www.realizedsound.net/josh
*/

Hi Nathan,

Thanks for this – I do appreciate the gesture.

I should also apologize for overstating my case. “Clever” is not such a terrible assessment, but “awful” was uncalled for.

Here, I will admit that I still carry some bad feelings from the original “NamedControl: better way” thread. My poor choice of words is largely because I don’t feel that my disagreement with that thread has been heard (which wasn’t a good way to handle that, just explaining where it came from).

It would have been accurate to say that SynthDef function args have certain advantages and disadvantages, and that NamedControl / \symbol.kr has different advantages and disadvantages, and that it’s up to the user to decide on balance which of those they prefer.

I can understand that you were excited about the advantages and trying to convince users to swim against the current of a coding pattern found everywhere in the documentation. Both of those could lead you to overstate the case (which I understand, as I did the same thing). Both of those also mean – be honest! – that the purpose of the thread was partly polemical. Polemics are IMO not quite productive here.

I think it would be helpful to rewrite the earlier post to be more fair. Basically… from this last comment, you’re showing that you understand what it feels like when someone comes for a way of doing things that you like very much. That’s exactly how I feel about “NamedControl: better way.” I know you weren’t deliberately doing that, but – sorry, I should let it go, but – it still bothers me. It’s certainly possible to advocate for \symbol.kr without telling other people that their way is too old-school, but that thread doesn’t quite avoid the temptation.

hjh

I’m no longer able to edit old posts from years ago.

I wouldn’t have edited the post anyway. From what I understand, you dislike my mini-article because in addition to arguing for NamedControls/Symbol:kr, I denigrated the argument style. My points were very specific, by identifying what I believe to be important problems with the argument style:

  1. that the i_/a_/t_ prefixes are ugly
  2. that the i_/a_/t_ prefixes can be invoked by accident
  3. that the rates: parameter of SynthDef splits the specification of arguments in two places
  4. that literal arrays in SynthDef arguments require repetitive code
  5. that trying to avoid repetitive code with something like freqs = (440 ! 8) leads to silent unexpected behavior

Aside from 1, these all seem like fairly objective descriptions of real design problems to me. Maybe you have counter-arguments or simply think they aren’t major issues, and if so I’d be happy to hear you out and healthily debate these points. But I simply don’t see where you’re getting “polemics” and “telling other people that their way is too old school” (I never said such a thing).

I fully agree that symbol.kr is better in several cases, and that for some users, it may even be generally better.

For the sake of public record, I’d like to redact some of the comments that had been in this space. I stand by my assertion that the article has an agenda beyond technical analysis, but the way I discussed that earlier wasn’t helpful. I apologize (again) for that.

As for the use cases: I do actually use NamedControl when it’s clearer (especially for arrayed controls). I have not adopted symbol.kr, and I won’t, because I’ve come over the years to feel that readability is more important than less typing (and I’ve had an RSI for 3 decades, if anyone should be looking to cut down on keystrokes, it’s me – but after years of trying to write as syntactically tight code as possible, I ended up preferring to slow down, write things out step by step, use keywords such as NamedControl – yep, spelling it out! – where appropriate to highlight what is really happening). Rate prefixes, I find aesthetically neutral; the prospect of abusing them accidentally is a valid point (yet somehow has never happened to me in a couple decades).

I had considered at one point writing a preprocessor to unify SynthDef input description, but ran out of time and didn’t get very far… so I do recognize that SynthDef function arguments are not an end-all be-all solution. But I like them for standard kr, non-arrayed controls, which (at least in my typical usage) counts for a large majority of synth inputs. As such I see no need to change it just because another style supports features that I’m not using.

hjh

I personally loved @nathan’s legendary thread on NamedControl: a better way to write SynthDef arguments… to date it’s the top thread of ALL TIME!

I’ve only used NamedControls since the day I read it.

While they certainly add a touch of mystery to the uninitiated… for them a var is far less distracting & wizardly at a glance.

If they ever need a solid primer to understand the genius of using them, @nathan’s article is the definitive source, just send them there.

However, at the same time I couldn’t agree with @jamshark70 more… early users come here for education, not to be mesmerized by the esoteric & enigmatic.