TL;DR New quark defining an alternate (I hope improved) syntax for SynthDef control inputs.
It’s not thoroughly tested! But it seems to cover a lot of territory already.
Quarks.install("https://github.com/jamshark70/ddwSynthArgPreprocessor");
thisProcess.recompile;
// then...
s.boot;
(
a = {
##freq = 440;
##amp = 0.1;
SinOsc.ar(freq, 0, amp).dup
}.play;
)
a.set(\freq, 220, \amp, 0.8);
a.release;
So… why?
The faults of using function arguments for SynthDef controls have been amply discussed: confusing behavior of expression defaults, weird prefixes for audio- and trigger-rate controls in particular.
The faults of NamedControl have been downplayed a bit – I personally struggle with them, so I’ve avoided NamedControl for the common case of a single-channel kr control. I do use NamedControl when it counts, especially for arrayed controls. (What are the faults? If you wish to define controls all at the top, names have to be given redundantly: var name = \name.kr
; if you dislike the redundancy, there’s a risk of error if you accidentally provide more than one default value for the same control name in different places.)
What would be nice, then, is a syntax that combines the advantages of arg-style (syntactic efficiency, and clearly separating control input definition from usage) and the advantages of NamedControl (clearer syntax for atypical rates, easier to understand default expressions).
I had this idea some years ago but wasn’t able to pull it together. But, recently, I was working on some code-generation classes involving Controls, and realized that I could use those insights for this problem.
So this version supports: ## name = default: rate, lag, spec;
.
-
name
should be a literal identifier. -
default
is an expression producing a number, or array of numbers. (Note that scsynth does not support a control-input default that depends on a server signal – control defaults always have to be numeric.) -
rate
should be one ofk
,a
,t
,i
. (Actually I didn’t doi
rate yet… oops.) -
lag
is an expression producing a number. (Actually I’m not sure if an arrayed control can have an array of lags… I suspect that it should, but I haven’t tested this yet.)-
lag
can depend on a server signal! It should be OK in this version, although there are probably some exotic cases where it wouldn’t work.)
-
-
spec
is an expression producing an object that answers to.asSpec
: e.g.,[0, 1, \lin]
or\freq
.
The default can be omitted; the “rate, lag, spec” also doesn’t have to be fully specified (you could write only rate, or “rate, lag”).
Also, it isn’t strictly required to put ##
controls at the top of the function.
(
SynthDef(\test, {
## freq = 440;
var sig = SinOsc.ar(freq);
## out = 0;
Out.ar(out, sig)
}).dumpUGens;
)
… produces a valid SynthDef. So if you prefer to have controls defined closer to the place where they are used, this is OK. (There is one exception: it’s impossible to support something like the following.)
(
SynthDef(\test, {
var sig;
## lagTime = 0.1;
## freq = 440;
sig = SinOsc.ar(freq);
lagTime = lagTime * 2;
## out = 0: k lagTime;
Out.ar(out, sig)
}).dumpUGens;
)
… because lagTime = lagTime * 2;
must come after the var
block, but ##
control inputs are var
s, and out
’s lag time depends on a non-var expression. Can’t be done. But, this is also a very weird scenario.
Now here’s a nice side effect of the logic to consolidate controls as much as possible:
SynthArgPreprocessor.enabled = false;
(
SynthDef(\wideGraph, {
var freq = \freq.kr(Array.fill(65, { exprand(200, 800) }));
var amp = \amp.kr(Array.fill(65, { rrand(0.05, 0.2) }));
Out.ar(\out.kr, SinOsc.ar(freq, 0, amp).sum)
}).add;
)
-> SynthDef:wideGraph
exception in GraphDef_Recv: exceeded number of interconnect buffers.
SynthArgPreprocessor.install;
(
SynthDef(\slimGraph, {
## freq = Array.fill(65, { exprand(200, 800) });
## amp = Array.fill(65, { rrand(0.05, 0.2) });
## out;
Out.ar(out, SinOsc.ar(freq, 0, amp).sum)
}).add;
)
// no server error!
The problem is that the NamedControl approach splits the freq and amp arrays into separate Control UGens, and this interacts with the UGen sorting logic to pull all the SinOscs up to the top, where each one requires its own wire buffer. SynthArgPreprocessor unifies blocks of controls into one UGen, as much as possible – here, freq, amp and out are all one Control, which in this case works better with the UGen sort (you get a long, narrow chain of SinOsc → MulAdd – minimal wire buffer usage). It’s a subtle problem that never really came up until it became common practice to split up controls. I’ve been thinking for a year or two now how to alleviate that problem without using literal arrays in function arg lists (which are really impractical for larger arrays).
Anyway… feel free to try it out, kick the tires – if you find any bugs, bring them up here or log a bug at Issues · jamshark70/ddwSynthArgPreprocessor · GitHub .
hjh