NamedControl named after non-valid identifier produces syntax error

This code:

SynthDef(\, { '$'.kr }).add;

produces a syntax error:

ERROR: syntax error, unexpected ASCII, expecting ELLIPSIS
  in interpreted text
  line 1 char 9:

  #{ arg $;
  	var	x8D0F1C0F =;
ERROR: Command line parse failed

It may seem odd to use a string that isn’t a valid identifier as a control name, but I have a generalized system for SynthDef parameters and would like to designate certain controls as special to avoid naming collisions. It’s not totally necessary that I have controls named this way to solve this problem, but it would be nice if it was possible.

I tried peeking into the SynthDef source to look for some call to .interpret but I couldn’t find it. Why does seemingly valid sclang code produce a syntax error?

I found it, but I don’t get it.

  • SynthDef:add
  • calls SynthDef:addDescFromDef
  • calls SynthDef:asBytes to compile the SynthDef to bytes, then calls SynthDescLib:readDescFromDef
  • calls SynthDesc:readSynthDef or SynthDesc:readSynthDef2 depending on the SynthDef version
  • both of which call SynthDesc:makeMsgFunc
  • which tries to decompile the SynthDef back into sclang code, but in a half-baked way that doesn’t always work
  • and then compiles and evaluates the code

Compiling, decompiling, then interpreting is super strange and unnecessary. Why does SynthDef do this?

It seems that the workaround is to use .send instead of .add.

The reason for makeMsgFunc is to make it more efficient to extract relevant values out of Events.

When producing an argument list from an Event, there would be three choices I can think of:

  1. Take all values from the Event, including defaults, and possibly even navigating upward through proto and parent chains.
  2. Or, iterate over the def’s controlNames list and pull values.
  3. Or, auto-generate a function to do the same work as option 2, but without the overhead of looping opcodes.

The first would be obviously inefficient: the OSC arg list would include values not relevant to the SynthDef. The second and third both omit irrelevant values. The third is roughly 2 times faster by benchmarking.

“Decompiling” – 1. If it did not extract the control name information from the binary stream, then you could not use .scsyndef files with Events. So it would not be an adequate solution to require a full SynthDef object. 2. I’m not 100% sure why the not-fully-functioning SynthDef object is preserved, when reading scsyndef from disk. In the case of .add, the SynthDef object is exactly the one that you started with, though, not a new decompiled def.

To the OP… It’s correct to note that nobody anticipated using an arbitrary ASCII string as a control name (and, incidentally, this is prohibited when specifying synth controls as function arguments).

Could you perhaps avoid naming collisions using a suffix rather than a prefix? abc_special rather than $abc?


Here is my benchmark, by the way.

SynthDef(\a, {
	var controls = Array.fill(20, { |i|"c" ++ i).asSymbol, 0)
	});,, 1, 0.1))

f =[\a].msgFunc;

// it isn't necessary for an event to supply all control values
e = (a1: 500, a5: 200, a10: 300, a8: 50, a14: 300);

e.use { bench { { f.valueEnvir } } }

time to run: 0.088346885999727 seconds.
time to run: 0.087817819000065 seconds.
time to run: 0.088859491999756 seconds.
time to run: 0.081405739000274 seconds.

d =[\a].controlNames;

bench { {
	var out = * 2); { |name|
		e[name] !? {
} };

time to run: 0.20323123100025 seconds.
time to run: 0.20330306100004 seconds.
time to run: 0.20486550900023 seconds.
time to run: 0.20460448999984 seconds.

It occurs to me that makeMsgFunc could check for control names that are not valid identifiers, and revert to the slower but more general function (based on the second benchmark).

But I wouldn’t advocate replacing the current approach altogether. Non-alphanumeric control names have never come up in the 20 years I’ve been using SC. We shouldn’t double the time it takes to build event arg lists in all cases, because of an exceptionally rare case. I wouldn’t mind a pre-check at msgFunc build time though.