Ndef as a fx chain: i'm stuck

This is a general problem with SynthDef.wrap, which is merely reflected in your use case. If you wrap the same function several times in the same synthdef you are responsible for renaming its arguments on every instancce, sadly. Somewhat more practical is to use NamedControls instead of arg, because the former are strings, so you can manipulate them more easily.

f = { arg ctrl; Poll.kr(Impulse.kr(1), ctrl) }

SynthDef("bad_idea", { SynthDef.wrap(f); SynthDef.wrap(f) }).add

// WARNING: Could not build msgFunc for this SynthDesc: duplicate control name ctrl

Unfortunately as discussed here with respect to out, NodeProxy doesn’t print that kind of warning about duplicate control names in its custom graph builder.

Semi-proper solution #1, assuming you want it to be the same control shared by both functions (in your case don’t but we’ll get to that later)

f = { Poll.kr(Impulse.kr(1), \ctrl.kr) }

SynthDef("one_ctrl", { SynthDef.wrap(f); SynthDef.wrap(f) }).add

In your case you need a function generator that renames the controls. Alas I don’t know how to offer a super-general version of this that inspects the function and does some magic, but as a leaky abstraction you can have

g = { arg i; { Poll.kr(Impulse.kr(1), ("ctrl" ++ i).asSymbol.kr) } }

SynthDef("two_ctrls", { SynthDef.wrap(g.(1)); SynthDef.wrap(g.(2)) }).add

x = Synth("two_ctrls")

s.queryAllNodes(true)

would show something like

     1102 two_ctrls
        ctrl1: 0 ctrl2: 0

lgvr123’s solution above is more transparent than this, but it may run in trouble because it’s just doing string replacements on the function source code. You might accidentally replace something other than an argument name that way, but I suppose it’s useable if you’re careful on that angle, i.e. give your args names that won’t clash e.g. with some methods names and what not.

This discussion reminds of defmacro in Lisp and its use in Nyquist to do score generation (score-gen).