BusPlug with (auto-routed) controls? Or how to \wrap or dock a Ndef (in)to another?

When you “plug” a Ndef y into one of the sources slots of another x Ndef’s, e.g. x[3] = y, the object that is plugged in Ndef’s x objects array is actually a BusPlug.

But this only routes the output of y to x's own bus. It does nothing for the controls of y, which I find somewhat limiting, because I end up juggling multiple objects either mentally or on-screen.

Ndef(\y, { arg freq = 555; 0.1 * SinOsc.ar(freq) })
Ndef(\x, { Silent.ar })
Ndef(\x)[1] = Ndef(\y)
Ndef(\x).play.edit // zero controls
Ndef(\x).set(\freq, 999) // does nothing, of course
Ndef(\y).set(\freq, 999) // still have to do it from here

I’d like some auto-routing magic that does make Ndef(\x).set(\freq work
for all controls of y.

At a brainstorming level, syntactically this could be something like a (new) NodeProxy role although the direction of the arrow looks a big wrong, visually, for the intended semantics…

Ndef(\x)[1] = \dock -> Ndef(\y)

So, has anyone written something that extends BusPlug so that it also “slaves” the controls of y to x, meaning map all of them to some controls in x?

Basically y would lose its own controls while “slaved”, but you’d be able to control it through x, which would provide a unified interface. I realize you could more or less do this without touching the Ndef themselves by some GUI docking of widgets in groups, but GUI docking support is rater non-existent in sclang, as far as I can tell, and I’m not talking about scide here, I know the IDE has docking for its own windows, but you can’t really dock sclang-generated windows either in the ide nor with each other, as far as I can tell.

A bit later, it occurred to me that to keep with the SC terminology, it would be best if I called it \wrap, based on the obvious analogy with SynthDef.wrap, rather than \dock. (SynthDef.wrap also collects the Controls from the “sub-function” into the “master list” of Controls of the enclosing SynthDef.) Perhaps, in those more familiar terms, someone else has considered it already, for Ndefs, I mean.

Well, this is not totally correct on the internals, in hindsight. For the details see this post.) What that actually means alas is that a new ProxySynthDef is compiled to do the copying even for something deceptively simple like Ndef(\x)[1] = Ndef(\y). So without breaking the current design much, my \wrap would have to handle arbitrary AbstractFunction expressions! It gets a bit scary.

1 Like

I thought a bit some more about this and basically there are two approaches that one can take using the current server capabilities.

  1. Move the wrapped Ndef to the output bus and inside the group of the wrapper. The last bit automagically solves the controls “slaving” issue, due to the way the server broadcasts control changes to nodes (including sub-groups) inside a group. There is a caveat to this however, namely that presently explicitly moving a Ndef to a specific bus can result in dangling bus references to it, regardless of reshaping mode. This approach alas also won’t help with Guis like NdefGui, which are unaware of any controls of groups that externally encompass a Ndef.

  2. Leave the wrapped Ndef where it is, but in the copying ProxySynthDef wrapper that gets created (anyway) add controls that are routed to the wrapped Ndef. This is a bit involved because the NodeMap of the wrapped Ndef would have to be inspected and busses of the appropriate types would have to be allocated on the server, which results in a bit of a “bus spam”. To support the reverse operation of unplugging or unwrapping in this approach, an explicitly copy of the old NodeMap would have to be kept and restored on “unplug”.

Approach #2 could be made a bit less bus-spammy if there were support for wholesale control diversion other than group nesting or explicit bus routing on the server. Basically the scheduling and control mapping are bundled together as one concept on the server (in the form of groups), while the outputs are unbundled from that, as busses. (Ndef basically re-bundles the busses with the groups, for a more functional-programming oriented approach.) However, the control broadcast aspect of groups and the order of execution that groups also dictate seem like could be orthogonal aspects on the server.