Ndef as a fx chain: i'm stuck

Disregard my rather failed attempts below the line, but which I’m leaving as an interesting exercise for why the solution looks like this:

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

SynthDef("funny_ctrls", {
	SynthDef.wrap({ (ctrl: "boo").use { f.value } });
	SynthDef.wrap({ (ctrl: "yoo").use { f.value } })
}).add;

x = Synth("funny_ctrls");

s.queryAllNodes(true)
)

This will create two controls of different names, as expected. So you just have to use envir ~vars for the control names (in NamedControl style) and no args.

The trick was to mostly emulate what inEnvir does but return a function that takes no args otherwise the graph builder chokes up.

You can even combine this approach with lgvr123’s idea above, but instead of generating the final synth directly by string substitution every time, you do that only once to generate “templates” like f above, which you can manually check for correctness, but you only need to do this once per template. After that, with the envir ~vars approach, they work as proper macro-like placeholder substitutions, i.e. you don’t risk replacing the wrong sub-string as the placeholders to be replaced are well-defined in the f “template” above when .used.

By the way, .asCompileString doesn’t see too deeply if your synth function calls others or uses variables. Never mind if the final control names are computed in some more intricate way. E.g.

(
c = ["boo", "yoo", "hoo"];

SynthDef("funny_ctrls", {
	c.do { |cn|	SynthDef.wrap { (ctrl: cn).use(f) } };
}).add
)

SynthDescLib.global.at(\funny_ctrls).def.func.asCompileString

returns

-> {
	c.do { |cn|	SynthDef.wrap { (ctrl: cn).use(f) } };
}

You may know the real control names from

SynthDescLib.global.at(\funny_ctrls).controlNames
// ->  -> [ boo, yoo, hoo ]

but it doesn’t always help find where to substitute. “Decompiling” the functions just with asCompileString to find the places to replace isn’t just risking “false positives”, i.e. replacing the wrong stuff, but also “false negatives”, i.e. not finding the actual places where the controls get created, but I do concede that lgvr123’s solution will probably work well in a lot of real-world cases.


Part kept for “historical purposes”, LOL:

I’m trying to fake defmacro via environment vars in SC. Would still be non quite transparent, but less horrible perhaps than my previous attempt. Alas the graph builder doesn’t like .inEnvir functions, even completely empty ones. Not sure why.

The basic idea would be this:

// simple illustration of the replacement inEnvir trick

f = { (~ctrl + "says hi.").postln }
f.() // tries to call with ~ctrl nil

g = { arg e; e.use { f } } // nope this won't work
g.((ctrl: "boo")).()

g = { arg e; f.inEnvir(e) } // this works
g.((ctrl: "boo")).() // boo says hi.

g = { arg e, f; f.inEnvir(e) } // f as arg too
g.((ctrl: "boo"), f).() // ok!!

Alas that’s as far as it goes. Even completely empty functions called .inEnvir upset the synth graph builder.

SynthDef("funny_ctrls", { SynthDef.wrap({}) }).add // ok meh

SynthDef("funny_ctrls", { SynthDef.wrap({}.inEnvir)}).add 
// The preceding error dump is for ERROR: a Control: wrong number of channels (0)

I don’t know how to fix or work around that, right now. I suppose it happens because the function (wrapper) returned by inEnvir has a variable number of args and no named ones

// attach the function to a specific environment
inEnvir { |envir|
	envir ?? { envir = currentEnvironment };
	^{ |... args| envir.use({ this.valueArray(args) }) }
}

So I guess the synthdef graph builder has no idea what to do with that.

SynthDef("funny_ctrls", { SynthDef.wrap({ |... args| })}).add 
// ^^ The preceding error dump is for ERROR: a Control: wrong number of channels (0)
1 Like