Ndef as a fx chain: i'm stuck

thanks for your help. its working with MidiSynth(\m1).put(210, \granular_reverb).set(...)
but im not able to get a 100% wet signal. will open another thread.
sorry for interrupting.

based on the code above, i think you would need to change your in arg

arg in=0

with something like

var in = In.ar(\out.kr, 2);

And then for your output if youā€™re using Out or ReplaceOut use this insteadā€¦

XOut.ar(\out.kr, \mix.kr(1), sig.tanh)

Iā€™m not 100% certain but I think this would work, at least this is how I understand JitLib handles the plumbingā€¦

Before I forget about this thread, Iā€™m going to add a ā€œtable of contentsā€ here for the several ways to skin this cat that have been discussed above. This will be a bit biased, I admit.

  1. If you only need to change the names of an already complied SynthDef, then you can do this quite safely by changing its .allControlNames, possibly using deepCopy too if you need several instances each with different control names. This is discussed in post #9 as makeSDclones. This techinque alone isnā€™t too useful for Ndef / NodeProxy roles (like \filer ->), but you can plug the resulting SynthDefs directly into NodeProxy sources slots.

  2. The previous technique can be adapted to NodeProxy roles by writing custom roles that essentially intercept the the ProxySynthDef object returned by .buildForProxy. This method has been discussed in post #20 under the names \mixM. \filterM, and \filterInM where a ā€œmass generatorā€ of such roles was given that can essentially take an existing role and add the automatic renaming functionality to it, producing a new role automagically.

The two related techniques above donā€™t require any extensions to the SC class library.

  1. If you need to combine several functions that produce SynthDef code into one SynthDef that doesnā€™t have control name conflicts, then the most general method is to install a mangler that intercepts the ControlNames as they get emitted to the SynthDef graph as it is being compiled. While this method does require some SC extensions, it does buy you the ability to SynthDef.wrap the same function multiple times in the same SynthDef e.g. with suffixed argument names for each invocation. Extension code for this method has been provided in post #13. To emphasize again: in difference to the technique used at the previous two points, this one produces a single resulting SynthDef, possibly from many instances of the same synth function. Although (in hindsight) a bit overkill for NodeProxy roles, this technique can also be used for them, as shown (as another application, if you will) in the 2nd part of post #14 as \mixN and \filterN. (Not much creativity with names, I admit.)

  2. Related to technique 3, you can also mangle the names in the function text code, but this approach is a little more brittle, because it might not find the control names to replace if they donā€™t show up in the function text directly, because e.g. they are obtained by calling yet another function. This technique has been discussed in post #2.

  3. If youā€™re willing to restrict yourself to using NamedControls invoked in \sym.kr style, a technique requiring simpler extensions is to alter / extend those Symbol methods a bit to do the mangling, e.g. to lookup some suffix from an environment and use inEnvir to evaluate the function in the right environment to get the suffix. This has been discussed in posts #10 and #12. A variation on this that essentially turns your SynthDef function in a macro of sorts, is to write ~foo.kr instead of \foo.kr and again to use inEnvir style evaluation for the replacement. The last approach requires no extensions whatsoever, but it does require one to change their source code.

I think I didnā€™t miss any major ideas / approaches discussed above in this summary. If I did, I apologize in advance. And please let me know if thatā€™s the case so I can include the missing bits in this summary (while I can still edit it, that is).

2 Likes

Thank you for all your replies ! I learned a lot. Finally I used the @Avid_Reader solution, very neat!

I can now use fx in any Ndef, I integrated the code in my Param quark. I made a video to show how to build a sequencer GUI, then I use the Ndef fx at minute 8:40

5 Likes

Thanks for the vote of confidence. I havenā€™t watched your full video, so can you spoil it for the impatient me with respect to which of the above solutions youā€™ve used, so I can consider packaging it as a quark maybe?


For Ndef slots controls renaming Iā€™ve added a feature to support some shared controls, in the meantime, for my own need. The syntax is a little odd, but it exploits the fact that array building has higher precedence in the sclang parser than ->, so you donā€™t have type many extra parentheses to force right-associativity. E.g. use case:

~filt = { |sig| Ringz.ar(sig, (\cutoff.kr(1) * 1000) + \lofreq.kr(100), 0.9, 0.5);};

~a = Ndef(\node) { LFSaw.ar(200, 0, 0.05) }; // sets ~a[0]

~a[1] = \filterM -> [~filt, \cutoff]; // only \lofreq will be numbered
~a[2] = \filterM -> [~filt, \cutoff]; // ibid

~a.edit;

\cutoff is shared contorls for both slots as Ndef normally does, but \lofreq is not. Basically, you supply an array whose first element is the ndef function, but the subsequent elements of this are are names of controls that are supposed to be (kept) shared. You can still use non-arrays as arguments, it will just make every control of that slot unique in that case, as in the previous versions of this role generator.

~a[3] = \filterM -> ~filt; // this one gets its separate cutoff too

~a.controlNames do: _.postln
/*
ControlName  P 1 wet1 control 1.0
ControlName  P 4 cutoff control 1
ControlName  P 5 lofreq1 control 100
ControlName  P 1 wet2 control 1.0
ControlName  P 5 lofreq2 control 100
ControlName  P 1 wet3 control 1.0
ControlName  P 4 cutoff3 control 1
ControlName  P 5 lofreq3 control 100
*/

For the curious: a syntax alternative would have been to addUniqueMethod the function so you could have written something like \filterM -> ~filt.common(\cutoff) because . also has higher precedence, but that approach ran into some SC bug with testing for the presence of addUnique-d methods later.)

So, if anybody needs this, hereā€™s version of those Ndef roles that supports those (partially) shared controls, with the array syntax.:

(~massPostmaglerInstaller = { arg newRolesSuffix = "M"; // so \mixM etc.
	var targetRoles = #[\mix, \filter, \filterIn];
	var defaultSkipNames = #["out", "i_out", "gate", "fadeTime"];
	var specificSkipNames = (mix: ["mix"], filter: ["wet"], filterIn: ["wet"]);
	var postmangler = { arg name, index, role, extraSkipNames;
		var skipNames = defaultSkipNames ++ (specificSkipNames[role] +++ index);
		skipNames = skipNames ++ (extraSkipNames.collect {|n| n.asString});
		name = name.asString;
		extraSkipNames.postln;
		skipNames.postln;
		if(skipNames.indexOfEqual(name).isNil) {
			name = name.asString ++ index;
			("Renamed::" + name).postln;
		} {
			("Skipped::" + name).postln;
		};
		name.asSymbol
	};
	var wrapperGen = { arg roleName, roleBuildFunc;  // curried targets
		{ arg func, proxy, channelOffset = 0, index;
			var psd;
			func = func.asArray;
			psd = roleBuildFunc.value(func[0], proxy, channelOffset, index);
			psd.allControlNames.do { arg cno;
				cno.name = postmangler.value(cno.name, index, roleName, func[1..]) };
			psd.allControlNames.do(_.postln);
			psd
		}
	};
	targetRoles.collect { arg roleName;
		var origBuildFunc = AbstractPlayControl.buildMethods[roleName];
		var newBuildFunc = wrapperGen.value(roleName, origBuildFunc);
		var newRoleName = (roleName.asString ++ newRolesSuffix).asSymbol;
		AbstractPlayControl.buildMethods.put(newRoleName, newBuildFunc);
		(newRoleName.asString + "installed").postln;
		[newRoleName, newBuildFunc]  // ret val somewhat irrelevant
	}
};

~massPostmaglerInstaller.()
)

In this case donā€™t watch the video, I donā€™t show any internal code, the goal is to show how to create a complex GUI sequencer with FX with only a few lines of high level code

I used the renaming of controlNames, thatā€™s the most easiest, quickest, cleanest solution =) Then I use Ndef(xx)[n] = \cloned_synthname where the synthdef have a In.ar and XOut.ar using the \out argument like you instructed


	cloneSynthDefWithIndexedArguments: { arg self;
		var sdc;
		var synthDef;
		var suffix = self.index;
		var synthDesc;
		synthDesc = SynthDesc(self.synthName);
		if(synthDesc.notNil) {
			synthDef = synthDesc.def;
			sdc = synthDef.deepCopy;
			sdc.name = synthDef.name.asString ++ suffix; // no eff without asString!
			sdc.allControlNames.do { arg cno;
				if(not(self.excludedArgs.includes(cno.name))) {
					cno.name = (cno.name.asString ++ suffix);
				} {
					// ("Skipped renaming" + cno.name + "in" + sdc).postln
				}
			};
			sdc // return whole sd clone to be collect-ed
		} {
			Log(\Param).debug("InsertFx.cloneSynthDefWithIndexedArguments: no synthDef: %", self.synthName);
			nil
		}
	},

Ok, but beware that if you In.ar(\out.ir) in a Ndef presently it has some odd effects like it will play immediately on bus 0. See e.g. this. Unless youā€™re also [re]numbering \out to \outX, which Iā€™m guessing you might be doing, since you didnā€™t add exception for ā€˜outā€™ in your code.

Thanks I didnā€™t know this bug. This is not causing real problems for me for the moment because the Ndef is used as a mixer so it produce no sounds on its own ^^
But I have one question, I thought that I must use the name \out else Ndef will not know which control name to set out bus, no ? Or with a numbered suffix, Ndef will also recognize it ?