Why is NodeProxy outputting on the wrong bus if In.kr(\out.ir) is used inside its function?

// reserve these for something else; JITLib should not touch 
c = Bus.control(s, 4);

y = NodeProxy.control(s, 1);
// make some self-triggering decay as simple illustration
y[0] = { var oi = In.kr(\out.ir(42)); if(oi > 0.01, oi * 0.999, 1.0) };

y.get(\out) // 4 -- seems to respect others, but...
y.trace

the output from NodeProxy goes to the wrong bus. Apparently that only happens when In.kr(\out.ir) is used that way inside the proxy function. The trace prints something like

  unit 1 In
    in  4
    out 0

 unit 10 Out
    in  0 1
    out

while the input is wired correctly, the output is not! Meaning NodeProxy corrupted (or overtook) a bus (number 0) reserved for something else. It doesn’t matter how I (try to) initialize that \out.ir. It’s still bus 0 that NodeProxy outputs to!

Why does that happen and how can I prevent it?

Well, I fixed it by rewiring it as

y = NodeProxy.control(s, 1)
y[0] = { var oi = y.kr(1); if(oi > 0.01, oi * 0.9999, 1.0) }

Interestingly this auto-generates an In that’s properly wired to its own out. Tracing that prints something like:

 unit 0 In
    in  9
    out 0.148356

unit 9 Out
    in  9 0.148341
    out

Still the question remains: why does NodeProxy output get so confused if you use In.kr(\out.ir) explicitly inside it?

I sorta figured out what’s going on. In the bugged example, somehow two out controls get created for the same synth. A tree dump for that looks something like

      1029 group
         1031 temp__01726152607_1416
           out: 14 gate: 1 fadeTime: 0.019999999552965 out: 0

There are two controls named out! And one (used by the Out) has the value 0, while the one used by In the value given by the NodeProxy index… I have no idea how this is even possible to have two controls with the same name in the same synth.

Apparently, it’s actually legal to build SynthDefs with duplicate name controls. When built directly with SynthDef that gives a hefty warning, but will still build it. Apparently, NodeProxy suppresses those warnings.

(SynthDef(\crazy, {
	var c1 = Control.names(\out).ir(1), i = In.kr(c1);
	var c2 = Control.names(\out).ir(3);
	Out.kr(c2, i) }).add)

prints out something like

-> SynthDef:crazy
WARNING: Could not build msgFunc for this SynthDesc: duplicate control name out

Your synthdef has been saved in the library and loaded on the server, if running.
Use of this synth in Patterns will not detect argument names automatically because of the duplicate name(s).

But it’s totally legal to instantiate that:

x = Synth(\crazy)

Dumping the sever tree has something like

      1032 crazy
        out: 1 out: 3

Interestingly the new style NamedControl doesn’t build duplicates.

(SynthDef(\crazyNot, {
	var c1 = \out.ir(1), i = In.kr(c1);
	var c2 = \out.ir;
	Out.kr(c2, i) }).add)

This will create a single out control. I guess a fix would be to change ProxySynthDef at line 127 (or so) where that happens in old-style:

				outCtl = Control.names(\out).ir(0) + channelOffset;

I’m still unsure why NodeProxy (e.g at line 615) has two controls one named out and one named i_out though. Anyway, just changing ProxySynthDef to read

				outCtl = \out.ir(0) + channelOffset; // fix for dupes

fixes the first kind of example, to the extent that one can properly use In.kr(\out.ir). Attempting to pass a non-zero initialization value would obviously produce an ERROR: NamedControl: cannot have more than one set of default values in the same control. The way the actual out bus index gets set is through a setting/mapping later on in the NodeProxy code.

An interesting side-effect of this on-line fixer in ProxySynthDef is that you can now Out.kr(\out.ir, ...) reliably from NodeProxies. If that is used as the last statement, it has as interesting side-effect of bypassing the autogenerated EnvGate. Out.kr itself returns zero (the actual output is a side-effect), so the EnvGate gets optimized away in the UGen graph generation due to its multiplication with zero. That may break some things that depend on an envelope being always present and enveloping all the proxy output though.

Yes, out is reserved in JITLib. It would be better to avoid creating a second one in your function.

The initial example here is a feedback loop, which is usually written with LocalIn and LocalOut. Those would work without needing to hack JITLib’s reserved keywords.

hjh

That only works if you don’t need to seed the LocalIn initial value with the initial value from the actual bus. Otherwise you have to read in the latter somehow. If we go with your theory that \out is really not supposed to be touched at all (even for reading), I suppose the alternative is to feed back the output via a named control, although that tends to clutter the synth’s interface and also needs external wiring that’s not included in the synth’s own function.

y = NodeProxy.control(s, 1)
y[0] = { arg oi; if(oi > 0.01, oi * 0.9999, 1.0) }
y <<>.oi y; // works too

At least this variant has the slight advantage over writing var oi = y.kr(1) that the synth’s function doesn’t need to refer to its externally given name (y). Me writing var oi = In.kr(\out.ir) was basically motivated by that issue too.

I suppose you’re somewhat correct that ProxySynthDef attempts to ban uses of out in the user’s function, but it only manages that at arg-level, not catching any NamedControl-style refences.

			// check for out key. this is used by internal control.
			func.def.argNames.do { arg name;
				if(name === \out) { hasOutArg = true };
				if(name === \gate) { hasGateArg = true };
			};

			if(isScalar.not and: hasOutArg)
			{
				"out argument is provided internally!".error; // avoid overriding generated out
				^nil
			};

Ahhhh… a valid use case that is currently not handled.

I suppose the best solution would be to search for out’s OutputProxy. If the user didn’t create an out control, then the search would fail and ProxySynthDef would be free to create a new control for it. If the OutputProxy is found, then ProxySynthDef could Out.ar(existingOutControl, signal).

The logic looks something like this. It’s kind of a deep dive, which is probably why ProxySynthDef doesn’t bother. It should probably be wrapped into a method, maybe controlChannelForName in SynthDef.

(
f = { |userFunc|
	SynthDef(\test, { |abc = 1, def = 2|
		var sig = SynthDef.wrap(userFunc);
		// this ControlName contains the index
		var outCtl = UGen.buildSynthDef.allControlNames.detect { |cname|
			cname.name == \out
		};
		var outIndex, outUGen, outChannel;
		var trig;
		if(outCtl.notNil) {
			outIndex = outCtl.index;
			outUGen = block { |break|
				UGen.buildSynthDef.children.do { |child|
					if(child.isKindOf(Control) and: {
						outIndex >= child.specialIndex and: {
							outIndex < (child.specialIndex + child.channels.size)
						}
					}) {
						break.(child);
					};
				};
				nil
			};
			outChannel = outUGen.channels[outIndex - outUGen.specialIndex];
		} {
			outChannel = NamedControl.kr(\out, 0);
		};
		trig = Impulse.kr(0);
		Poll.kr(trig, outChannel);
		FreeSelf.kr(trig <= 0);
	}).play;
};
)

f.({ |out = 300| "defined out arg as 300".postln });
defined out arg as 300
-> Synth('test' : 1003)
UGen(OutputProxy): 300  // OK!

f.({ var out = NamedControl.kr(\out, 400); "defined out as NamedControl = 400".postln });
defined out as NamedControl = 400
-> Synth('test' : 1004)
UGen(OutputProxy): 400  // OK!

f.({ "didn't declare out".postln });
didn't declare out
-> Synth('test' : 1007)
UGen(OutputProxy): 0  // OK!

The NodeProxy would still assign its own value to out but then it would be accessible in the user function.

I think the above will not handle arrayed controls though. Out of time for now. Actually it does! Because a Control’s array of OutputProxies doesn’t reflect the structure of arrayed controls – it’s flat – so the + works as desired.

f.({ |a = #[1, 2, 3], out = 500| "out arg = 500, after array".postln });
out arg = 500, after array
-> Synth('test' : 1001)
UGen(OutputProxy): 500

So the above logic should be generally OK.

hjh

1 Like

Feeling like procrastinating – here is a sketch that takes care of the problem for out.

@julian – What do you think about this?

Summarizing the issue, it is that the current approach in ProxySynthDef, of always creating its own out control, means that there is no way for the user’s synthdef to have access to the output channel and have the proxy play on the correct channel. The solution here is to look at all existing control names in the def, and trace from this to the correct OutputProxy channel of whichever Control object is responsible for this input. If none is found (which would be most of the time), the ProxySynthDef can make its own out control. Otherwise, it can create the Out UGen using the existing control created by the user.

(
s.waitForBoot {
	c = Bus.control(s, 10);
	
	Ndef(\x, { |out|
		In.kr(out)
	});
};

Ndef(\x).trace

TRACE 1001  temp__0x-129297539_1007    #units: 9
  unit 0 Control
    in 
    out 10

... snip...

  unit 8 Out
    in  10 0
    out
)

In principle we should do the same thing for gates, but I don’t understand the logic.

hjh

1 Like

Although… Having investigated all that, it then occurs to me: Is there anything wrong with using the \filter proxy role? Then the original value from the bus is passed in directly, not as a bus number.

y = NodeProxy.control(s, 1);

// the first argument receives In.ar/kr(out)
y[0] = \filter -> { arg oi; if(oi > 0.01, oi * 0.999, 1.0) };

Haven’t tested this, but in theory this should do what you want, without requiring a new feature. (It might still be a good new feature, but AFAICS it isn’t strictly necessary.)

hjh

Just as a first reply (I just saw this discussion). If you want to read the bus value on the very same bus, you always have to use an InFeedback UGen. This is independent of NodeProxy. But probably I am missing something …

The specific example here was for control rate, where In and InFeedback behave basically the same. (Control buses aren’t cleared at the start of a control block, so the value is always available.)

hjh

ah sure, ok --------.

It might be that the reason is that the mix between Control and NamedControl is not always consistent.

(
a = {  
	var out1 = Control.names(\out).kr(123);
	var out2 = NamedControl.kr(\out, 42);
	[out1, out2].poll
}.play
)


a.set(\out, -42); // this sets the first, but not the second

There are different levels at which one could solve the issue at hand. One would be to make ProxySynthDef use NamedControl instead of Control. The other, more complicated but probably better, would be to have NamedControl look up if there are instances of Control to which a given name corresponds and then return that instead.

In SynthDef creation, Controls are just dummy objects. Does anyone know how to get a control object for a given control index/key of a SynthDef? This is the tricky bit for the “better” solution.

NamedControl implements a private (of sorts) dictionary. Meaning it doesn’t know about any ControlNames it didn’t see pass through its interface. It’s probably hard to make it know about everything, unless you hook all the API calls for the lower layer stuff to ping back to NamedControl. And even then, it’s not totally clear deduplication is always desirable. See e.g. the case of the NodeProxy roles name mangler I wrote here when the names that go to ControlName may actually be duplicated, but they can be mangled differently at different times, depending on a global (context) setting.

Yes, exactly, when I wrote NamedControl I didn’t go through the SynthDef because that was too complicated at that moment. But the information is certainly there, because scsynth can decode it. It just lacks a good interface on the sclang side. So I wonder if anyone has found a good way of reconstructing a Control object that points to the correct slot in the SynthDef.

The first steps are:

SynthDef(\x, { |out = 9| Out.ar(out, 0) }).allControlNames 
// -> [ ControlName  P 0 out control 9 ]
x = SynthDef(\x, { |out = 9| Out.ar(out, 0) }).allControlNames.first; // just look at that one now
x.index // this is - possibly - the offset index in the SynthDef
x.argNum // this might be the maximum so far of control indices, but not sure

Here I usually get stuck …

From my understanding of the SynthDef internals, this doesn’t have a single answer because Controls with duplicate names can be added. You can see why that was allowed because for Control arrays, i.e. multi-channel controls, every control except the first in the array is named as question-mark (“?”) literally! I mean on the sever. So there’s that issue of deciding what to return from a such a search interface for names inside SynthDef controls.

Frankly, it would have made a bit more sense to me to add some postfix index number to the names such array Controls instead of calling them “?”.

If you’re merely asking about the meaning of those fields. that is actually simple, at least after the SynthDef is fully built. Here’s an example with a deliberate dupe:

d = SynthDef(\aa, { arg x = #[1, 2, 3], y = #[55, 66]; Control.names(\x).kr(44) })

d.allControlNames do: _.postln

/* ->
ControlName  P 0 x control [ 1, 2, 3 ]
ControlName  P 3 y control [ 55, 66 ]
ControlName  P 5 x control 44
*/

The index field (first thing printed after “P”) is the index in the SynthDef control array as will be
stored on the server. So the “x” at index 0 is not the same as the “x” at index 5.

As for argNum, which isn’t actually printed above:

d.allControlNames collect: _.argNum
// -> [ 0, 1, 2 ]

argNum is redundant when you have access to allControlNames, but not when you look at a single ControlName object in separation. argNum basically maps back to the array index in allControlNames. The main reason why argNum exists as a field is probably that SynthDef.buildControls code (which is only called for controls built from function args) is structured to split the work by type of Control, e.g. there are separate chunks of code to deal with triggers, audio etc. and these chunks access subcollectons as in:

var arguments = Array.newClear(controlNames.size);
// ...
var trControlNames = controlNames.select {|cn| cn.rate == 'trigger' };
// ...
			trControlNames.do {|cn, i|
				cn.index = index;
				index = index + cn.defaultValue.asArray.size;
				arguments[cn.argNum] = controlUGens[i];

By the way, you can see there that the size of the sub-array of a given Control only explicitly stored in cn.defaultValue.asArray.size, i.e. the size of the defaultValue array for a given Control.

Since controlNames is actually cleared when the SynthDef is built and only allControlNames is retained, we should probably check what happens with argName in a SynthDef wrap situation, while things are still being built.

Yeah, those argNums are relative to the innermost SynthDef being wrapped, i.e. index into controlNames not allControlNames really:

(
SynthDef(\aa, { arg x = #[5, 7], y = #[66, 77], a_i;
	SynthDef.wrap { arg t_t = 1, z = #[111, 222];
		UGen.buildSynthDef.controlNames do: _.postln;
		UGen.buildSynthDef.controlNames.do { |cn| cn.argNum.postln };
		UGen.buildSynthDef.allControlNames do: _.postln;
	};
	UGen.buildSynthDef.allControlNames.do { |cn| cn.argNum.postln };
})
)

/*
ControlName  P 5 t_t trigger 1
ControlName  P 6 z control [ 111, 222 ]
0
1
ControlName  P 1 x control [ 5, 7 ]
ControlName  P 3 y control [ 66, 77 ]
ControlName  P 0 a_i audio 0.0
ControlName  P 5 t_t trigger 1
ControlName  P 6 z control [ 111, 222 ]
0
1
2
0
1
*/

In a wrapping situation argNum is meaningless in the outside synthDef except for its own controlNames. For the interior SynthDefs, it’s basically garbage info once it reached the outer def because the inner arrays are destroyed by that point. Basically, argNums are temp numbers, not really useful after the stack of SynthDef.wrap stack is popped one or more times. (You could actually use it to detect that the SynthDef used wrap inside, by detecting non-monocities in final argNums, but I’m not sure how that info is useful.)

Also noteworthy that buildControls emits controls ControlName.index-grouped by type, so those indexes don’t necessarily coincide with the function argument order that was passed to SynthDef and also not with the array order in the allControlNames. You can see that above happen with a_i that gets ControlName.index-reordered (to index 0) before the kr-rate controls. The ControlName.index order is always: ir, tr, ar, kr. I’m not sure if this was done merely for esthetic reasons or if the sever has some such ordering expectation. I strongly suspect it was merely for esthetic reasons because it doesn’t index-group past wrap boundaries, e.g.

(
d = SynthDef(\aa) { arg x = #[5, 7]; SynthDef.wrap {
	arg z = #[111, 222], a_i; Out.ar(0, 0) }}
)

d.allControlNames do: _.postln

/*
ControlName  P 0 x control [ 5, 7 ]
ControlName  P 3 z control [ 111, 222 ]
ControlName  P 2 a_i audio 0.0
*/

If I remember correctly, the ordering happens for optimization. As we access them from inside, befor the buildControls stage, this should not matter.

From my understanding of the SynthDef internals, this doesn’t have a single answer because Controls with duplicate names can be added.

It would be ok to just return the first matching one. We just need a Control instance that points to the right index, so we can reuse that.

Not really. The buildControls happens before the function passed to the SynthDef is executed. The function is actually fed the controls as its actual inputs. That’s how the whole magic happens that the function builds the graph.

		result = func.valueArray(prependArgs ++ this.buildControls);

The question-mark stuff with array controls that I was talking about earlier only happens a bit later (indeed) at the SynthDesc lib stage (i.e. after the SynthDef is .added). For example, to recap & extend the duplicate-names test:

d = SynthDef(\aa, { arg x = #[1, 2, 3], y = #[55, 66]; Control.names(\x).kr(44) })

d.allControlNames do: _.postln

/* ->
ControlName  P 0 x control [ 1, 2, 3 ]
ControlName  P 3 y control [ 55, 66 ]
ControlName  P 5 x control 44
*/

d.add // warns about dupes.

d.desc

/*
-> SynthDesc 'aa' 
Controls:
ControlName  P 0 x control [ 1.0, 2.0, 3.0 ]
ControlName  P 1 ? control 2.0
ControlName  P 2 ? control 3.0
ControlName  P 3 y control [ 55.0, 66.0 ]
ControlName  P 4 ? control 66.0
ControlName  P 5 x control 44.0
*/

At the ‘desc’ stage, there’s some further control reordering, meaning the internal array gets regenerated in the index order that was saved inside the ControlName objects:

(
d = SynthDef(\aa) { arg x = #[5, 7]; SynthDef.wrap {
	arg z = #[111, 222], a_i; Out.ar(0, 0) }}
)

d.allControlNames do: _.postln

/*
ControlName  P 0 x control [ 5, 7 ]
ControlName  P 3 z control [ 111, 222 ]
ControlName  P 2 a_i audio 0.0
*/

d.add

d.desc

/*
-> SynthDesc 'aa' 
Controls:
ControlName  P 0 x control [ 5.0, 7.0 ]
ControlName  P 1 ? control 7.0
ControlName  P 2 a_i audio 0.0
ControlName  P 3 z control [ 111.0, 222.0 ]
ControlName  P 4 ? control 222.0
*/

But the latter is actually a different collection. d.allControlNames is not the same as d.desc.controls. Confusingly there’s also d.desc.controlNames that only has the name strings, not
the ControlName objects. The allControlNames array in the SynthDef proper doesn’t get rebuilt even on add.

d.allControlNames do: _.postln // same as before calling d.add

d.desc.controls do: _.postln // this is the one with question-marks

d.desc.controlNames do: _.postln // just -> [ x, z, a_i ]

By the way, the argNums are completely discarded at the desc stage.

d.desc.controls.do { |c| c.argNum.postln } // posts just nils

SynthDesc also comes with its own lookup and name-deduplication (of sorts) as desc.controlDict. NamedControl obviously cannot rely on the desc stuff to exist.

I’ve also checked the Server-Command-Reference but it is silent on duplicate name controls. I’m guessing the behavior is to set just the first matching name. Setting by numerical index instead of name is possible in that protocol though.

Yeah, the server just uses the first name in order on a match:

(
d = SynthDef(\aa) { arg x = #[5, 7];
	Poll.kr(Impulse.kr(0.3), x, "Top x:");
	SynthDef.wrap {	arg x = 111;
		Poll.kr(Impulse.kr(0.3), x, "Bot x:");
	};
	Out.ar(0, 0) }
)

d.allControlNames  // two x's

d.add;

x = Synth(d) // this fails in an obscure way "/s_new wrong argument type"

// But manual use of such synths is totally possible, even with name addressing

s.sendMsg("/s_new", "aa", s.nextNodeID, 0, 1, "x", 66) // sets just first 'x'

s.sendMsg("/n_set", n, 2, 55); // can set others by index of course

So I think NamedControl should emulate server behavior, i.e. find the first one that matches by name in the allControlNames. A nice feature to have would be for it search for the rest of the matches too and warn is more exist like SynthDesc does.

I had posted code earlier that traces from a control name to the ugen channel:

I had thought it might be useful…?

hjh

Basically Julian would rather have NamedControl load all the current controls in its private map where it can do the equivalent of UGen.buildSynthDef.allControlNames.detect ... . for everything. Right now NamedControl knows nothing about the other ones:

(
d = SynthDef(\aa) { arg x = #[5, 7];
	Poll.kr(Impulse.kr(0.3), x, "Top x:");
	\x.kr(12); // no complaints by the way, even though this shadows arg x
	Poll.kr(Impulse.kr(0.3), \x.kr, "Bot x:");
	Out.ar(0, 0) }
)

d.allControlNames // yep dupe on x name
// -> [ ControlName  P 0 x control [ 5, 7 ], ControlName  P 2 x control 111 ]

One thing that is completely missing from NamedControl’s source file is any reference to UGen.buildSynthDef.controlIndex; which can be used to detect when any controls were added externally, i.e. bypassing NamedControl’s own interface.

Well, this is the hard part. I can get the ControlName because that’s stored in allControlNames (and detect external changes to that with a little bookkeeping), but I can’t easliy get the actual Control object that was created externally, because that was just written somewhere to the graph. But your NamedControl class expects to be able to map from string names to that actual Control object (not to a ControlName) for some reason that’s not totally clear to me since making changes to these is mostly prohibited after they are emitted. I’m guessing you wanted to avoid the duplication of info that ControlName objects introduce since e.g. the default values are saved both there and in the actual Control in the standard scheme of doing that…

I guess I could add a map from ControlNames to Controls… or I could create proxy objects, but that’s quite a bit of typing. The actual Control-like objects auto-emit themselves to the graph when created, so that’s a no-no to make some of those for stuff that’s to be imported on updates into NamedControl.

Ok, I know how to do this, but it’s a bit crazy. In NamedControl you also need to keep a pointer (call it glp) to the last emmitted UGEN you have scanned previously. When the size of allControlNames changes a scan the graph from glp forward looking for ugens that have the isControlUGen flag set!!! (This is how SynthDesc finds the darn things, see e.g. line 362
if (ugenClass.isControlUGen) { ....) That’s really the only way to find the actual Control objects. Then after importing them, set glp to the current end of the graph; it’s worth remembering that the graph is just stored as a flat ‘children’ array. So basically you need to track both changes
to the size of allControlNames and keep a scanning pointer in the graph.

The original Control API looks a bit crazy with the split between Control.names that adds control names to the synthDef but does not emit the actual UGen to the graph until something like Control.kr is called. The reason why it’s like that is that it first parses all the args of the function in addControlsFromArgsOfFunc where only ContolNames are actually created, and then tries to group them by (ir, kr etc.) type and emit fewer multi-channel UGens in buildControls. So it’s basically two-pass approach over the arg list.

Unfortunately what that means is that three’s not a a 1:1 mapping between ControlNames and Controls, so that kinda breaks an assumption in your NamedControl API, if it were to import the standardly generated Controls. Here’s a simple illustration where there’s a single Control emitted for two ControlNames.

d = SynthDef(\boo) { arg c1 = 11, c2 = 22; Out.kr(12, c1+c2) }
d.dumpUGens

/*
[ 0_Control, control, nil ]
[ 1_+, control, [ 0_Control[0], 0_Control[1] ] ]
[ 2_Out, control, [ 12, 1_+ ] ]
*/

d.allControlNames
// -> [ ControlName  P 0 c1 control 11, ControlName  P 1 c2 control 22 ]

d.children[0].values // -> [ 11, 22 ]

For contrast. NamedControl emits two separate Control ugens for something like that:

d = SynthDef(\yoo) { Out.kr(12, \c1.kr(11) + \c2.kr(22)) }
d.dumpUGens

/*
[ 0_Control, control, nil ]
[ 1_Control, control, nil ]
[ 2_+, control, [ 0_Control[0], 1_Control[0] ] ]
[ 3_Out, control, [ 12, 2_+ ] ]
*/

So it would be quite a bit of work to “sub-index” into objects generated by the standard arg-parsing API in your NamedControl.