Dangling bus references in Ndef sources slots

After looking at how something like Ndef(\copyk2, Ndef(\kr2)) is implemented, meaning by compiling a ProxySynthDef that does the copying, which alas has a hardcoded value of \kr2's bus, I wondered if it’s possible to suffer from dangling bus reference or if that’s handled automatically in case of Ndef bus changes.

The answer is that it’s not handled automatically, at least on manual forced moves e.g.

// doing it with control busses because .get doesn't work for audio ones
Ndef(\kr2, { SinOsc.kr([440, 550]) });
Ndef(\copyk2, Ndef(\kr2))

Ndef(\kr2).bus.get; Ndef(\copyk2).bus.get // in sync
// Bus control index: 4 values: [ 0.9155501127243, -0.61077904701233 ].
// Bus control index: 6 values: [ 0.9155501127243, -0.61077904701233 ].

b = Bus.control(s, 2) // -> Bus(control, 8, 2, localhost)
Ndef(\kr2).bus = b // force move; doesn't take effect until you re-send it!
Ndef(\kr2).send // now it's on its new bus... but

Ndef(\kr2).bus.get; Ndef(\copyk2).bus.get
// Bus control index: 8 values: [ -0.62399613857269, 0.056934379041195 ].
// Bus control index: 6 values: [ -0.0, 0.0 ].

The “stub” ProxySynthDef that was compiled into objects[0] of Ndef(\copyk2) has a hardcoded bus number for the old bus number (4), seen on the first line of the graph:

Ndef(\copyk2).objects[0].synthDef.dumpUGens
/*
-> a ProxySynthDef
temp__0copyk2-1030064585_1029
[ 0_In, control, [ 4 ] ]
[ 1_Control, control, nil ]
[ 2_Control, control, nil ]
[ 3_EnvGen, control, [ 1_Control[0], 1.0, 0.0, 2_Control[0], 2, 0, 2, 1, -99, 1.0, 1.0, 1, 0, 0.0, 1.0, 1, 0 ] ]
[ 4_*, control, [ 0_In[0], 3_EnvGen ] ]
[ 5_*, control, [ 0_In[1], 3_EnvGen ] ]
[ 6_Control, scalar, nil ]
[ 7_Out, control, [ 6_Control[0], 4_*, 5_* ] ]
*/

To make this a question still: what are some strategies for avoiding this? Roughly when does a Ndef gets its bus number changed automatically? I’m guess if it needs to expand its channels (the \elastic business) but doesn’t have room to do so without moving its bus?

Actually, if you set reshaping to \elastic it does work properly to rebuild the dependents, at least when the bus size changes!

Ndef.clear
Ndef(\kr2, { DC.kr([44, 55]) }).reshaping = \elastic;
Ndef(\copyk2, Ndef(\kr2)).reshaping = \elastic;

Ndef(\kr2, { DC.kr([91, 92, 93]) });
Ndef(\kr2).bus // -> Bus(control, 4, 3, localhost)

Ndef(\copyk2).bus.get // Bus control index: 7 values: [ 91.0, 92.0, 93.0 ].
Ndef(\copyk2).bus // -> Bus(control, 7, 3, localhost)

I’m rather impressed!

However, the original example, with a manual bus move that stays at the same size still doesn’t get detected, even with \elastic. But I suppose this isn’t that much of a concern in typical use of Ndefs.

Ndef.clear
Ndef(\kr2, { DC.kr([44, 55]) }).reshaping = \elastic;
Ndef(\copyk2, Ndef(\kr2)).reshaping = \elastic;

Ndef(\kr2).bus // -> Bus(control, 0, 2, localhost)
Ndef(\kr2).bus.get // Bus control index: 0 values: [ 44.0, 55.0 ].

b = Bus.control(s, 2) // -> Bus(control, 4, 2, localhost)

Ndef(\kr2).bus = b
Ndef(\kr2).send

Ndef(\copyk2).bus.get // Bus control index: 2 values: [ 0.0, 0.0 ].