Ndef as a fx chain: i'm stuck

Note: earlier version was exhibiting a bug with NdefMixer; likely it would have happened with explicit use of ProxySpace as well, although I’ve stayed away from using that one explicitly myself insofar. I’ve added a check for that now in the first method below (Symbol.mangle).

I’ve got the NamedControl pre-mangling duplication check solved, i.e. now it’s a post-mangling check as it should be. It was quite a bit of code to change for that. Basically it needs to flag whether the mangling happened already in NamedControl.

+ Symbol {

	mangle {
		if(currentEnvironment.notNil) {
			if(~controlNameMangler.notNil) {
				// This is mostly a fix for NdefMixer which creates ControlNames
				// while use()-ing ProxySpace. And in ProxySpace every environment
				// variable key is mapped to a non-nil value! Furthermore, default
				// values differ between keys, so can't check even "==" equality
				// with the value mapped to a "random" key. But it's reasonably safe
				// to check that a random key is mapped to a non-nil value, to
				// detect proxy spacess.
				//this.dumpBackTrace;
				if(~aiospd345fjaiohtgXXO.isNil) {
					//("cnm:" + ~controlNameMangler + "this:" + this).postln;
					^~controlNameMangler.value(this).asSymbol.postln
				} {
					// "Detected ProxySpace".postln;
				}
			}
		}
		^this
	}

+ ControlName {

	*new { arg name, index, rate, defaultValue, argNum, lag, preMangled = false;
		^super.newCopyArgs(
			if(preMangled) { name.asSymbol } { name.asSymbol.mangle }, 
			index, rate, defaultValue, argNum, lag ? 0.0)
	}
}


+ SynthDef {

	// this is actually called from Control with a ControlName object
	// after it is built, so it needs no changes really, just noting that here
	addControlName { arg cn;
		controlNames = controlNames.add(cn);
		allControlNames = allControlNames.add(cn);
	}

	// allow incremental building of controls
	// CHANGED: all these (except addNonControl) are called from NamedControl,
	// so they now need to know if preMangled
	addNonControl { arg name, values, preMangled = false;
		this.addControlName(ControlName(name, nil, 'noncontrol',
			values.copy, controlNames.size, 0.0, preMangled));
	}
	addIr { arg name, values, preMangled = false;
		this.addControlName(ControlName(name, controls.size, 'scalar',
			values.copy, controlNames.size, 0.0, preMangled));
	}
	addKr { arg name, values, lags, preMangled = false;
		this.addControlName(ControlName(name, controls.size, 'control',
			values.copy, controlNames.size, lags.copy, preMangled));
	}
	addTr { arg name, values, preMangled = false;
		this.addControlName(ControlName(name, controls.size, 'trigger',
			values.copy, controlNames.size, 0.0, preMangled));
	}
	addAr { arg name, values, preMangled = false;
		this.addControlName(ControlName(name, controls.size, 'audio',
			values.copy, controlNames.size, 0.0, preMangled))
	}

}


+ NamedControl {

	*new { arg name, values, rate, lags, fixedLag = false, spec;
		var res;

		this.initDict;

		 /* just this line is CHANGED in this whole method (but more in init) */
		name = name.asSymbol.mangle;

		if (spec.notNil) {
			spec = spec.asSpec;

			if (values.isNil) {
				values = spec.default;
			};
		};

		res = currentControls.at(name);

		lags = lags.deepCollect(inf, {|elem|
			if (elem == 0) { nil } { elem }
		});

		if (lags.rate == \scalar) {
			fixedLag = true;
		};

		if(res.isNil) {
			values = (values ? 0.0).asArray;
			res = super.newCopyArgs(name, values, lags, rate, fixedLag).init;
			currentControls.put(name, res);
		} {
			values = (values ? res.values).asArray;
			if(res.values != values) {
				Error("NamedControl: cannot have more than one set of "
					"default values in the same control.").throw;
			};
			if(rate.notNil and: { res.rate != rate }) {
				Error("NamedControl: cannot have  more than one set of "
					"rates in the same control.").throw;
			};

		};

		if(res.fixedLag and: lags.notNil) {
			if( res.lags != lags ) {
				Error("NamedControl: cannot have more than one set of "
					"fixed lag values in the same control.").throw;
			} {
				^res.control;
			}
		};

		if(spec.notNil) {
			res.spec = spec; // Set after we've finished without error.
		};

		^if(lags.notNil) {
			res.control.lag(lags).unbubble
		} {
			res.control
		}
	}

	init { /* CHANGED: all callbacks to buildSynthDef need to pass preMangled: true */
		var prefix, str;

		name !? {
			str = name.asString;
			if(str[1] == $_) { prefix = str[0] };
		};

		if(fixedLag && lags.notNil && prefix.isNil) {
			// not sure why next line doesn't pass lags, by the way (might be no-op downstream)
			buildSynthDef.addKr(name, values.unbubble, preMangled: true);
			if(rate === \audio) {
				control = LagControl.ar(values.flat.unbubble, lags)
			} {
				control = LagControl.kr(values.flat.unbubble, lags)
			};
		} {
			if(prefix == $a or: {rate === \audio}) {
				buildSynthDef.addAr(name, values.unbubble, preMangled: true);
				control = AudioControl.ar(values.flat.unbubble);

			} {
				if(prefix == $t or: {rate === \trigger}) {
					buildSynthDef.addTr(name, values.unbubble, preMangled: true);
					control = TrigControl.kr(values.flat.unbubble);
				} {
					if(prefix == $i or: {rate === \scalar}) {
						buildSynthDef.addIr(name, values.unbubble, preMangled: true);
						control = Control.ir(values.flat.unbubble);
					} {
						buildSynthDef.addKr(name, values.unbubble, preMangled: true);
						control = Control.kr(values.flat.unbubble);
					}
				}
			};
		};

		control = control.asArray.reshapeLike(values).unbubble;
	}
}

With all that now this bit works properly:

(~swrap = { arg func, suffix;
	var envir  = (controlNameMangler: { arg name; (name ++ suffix).postln; });
	SynthDef.wrap { envir.use { func.value } }; // This eats args!
})

~f2 = { SinOsc.ar(\freq.ar(333)) } // works now because it deduplicates suffixed
d = SynthDef(\LRtest, { Out.ar(0, [~swrap.(~f2, "Left"), ~swrap.(~f2, "Right")]) })
d.allControlNames