Bug with Ndef <<> operator?


I’m running SC 3.11.2 on Mac OS 10.14.6, and I’m experiencing a weird problem when evaluating several times the <<> operator.

Here is a small example

BusPlug.defaultReshaping = \elastic;

Ndef(\sound, {
	(\in1.ar([0,0]) * \in2.ar([0,0])).tanh

Ndef(\a, { SinOsc.ar(MouseX.kr(300, 1000, 1) * [1, 1.2]) });
Ndef(\b, { LFNoise2.ar(MouseY.kr(3, 1000, 1) * [1, 1.2]) });

Ndef(\sound) <<>.in1 Ndef(\a);

// after the following line, there's some sound
Ndef(\sound) <<>.in2 Ndef(\b);

// evaluate the following line -> silence
Ndef(\sound) <<>.in1 Ndef(\a);

Do you experience the same ? Did I misunderstand anything ?



Same (and not-as-expected) behaviour here too. Eager to understand why.

There are a few problems happening - this is a case that JITlib does NOT handle well.

Here’s the graph just after I’ve set my three Ndefs, but before any mapping:

Everything looks okay, though we can already see one problem brewing: \a and \b are running AFTER \sound.

Here’s the graph after mapping Ndef(\a) to in1:

The problem from before has hit us: \a is running after \sound in the graph - so we’re reading from our bus before we’ve written any sound to it.

It gets worse… This is the graph after re-setting both of our ins (e.g. setting them a second time). Now, we somehow have some brand new 2-channel control buses mapped to our inputs… But, our corresponding synths are still playing out to their old audio buses.

This bug is pretty deep in JITlib / bus mapping code. You can easily see the side-effects, but I don’t know the exact cause… For example, before I map any buses, I have:

> Ndef(\sound).controlNames[0].dump;

Instance of ControlName {    (0x7fecd1d40dc8, gc=6C, fmt=00, flg=00, set=03)
  instance variables [6]
    name : Symbol 'in1'
    index : Integer 0
    rate : Symbol 'audio'
    defaultValue : instance of Array (0x7fecc36140a8, size=2, set=2)
    argNum : Integer 0
    lag : Float 0.000000   00000000 00000000

Looks correct: a two-channel audio-rate input. But after I map:

> Ndef(\sound) <<>.in1 Ndef(\a);
> Ndef(\sound).controlNames[0].dump;

Instance of ControlName {    (0x7fec92528788, gc=F8, fmt=00, flg=00, set=03)
  instance variables [6]
    name : Symbol 'in1'
    index : nil
    rate : Symbol 'control'
    defaultValue : instance of Array (0x7fecb1c6c7c8, size=2, set=2)
    argNum : nil
    lag : Float 0.000000   00000000 00000000

IIRC this part of the bug is triggered by \elastic reshaping: if you skip elastic, you won’t get this audio / control rate mix-up. However, the node ordering problem still remains - and this part is not easy to solve, there is no declarative, guaranteed way to set the node order of NodeProxy's, so this audio input mapping use-case is effectively broken without putting in some hacks yourself…

If you want this use-case to work, you can use Ndef:orderNodes to set the node order AFTER you’ve done all your mapping.

You’ll need do this after you’ve started all your Ndefs, and you’ll need to explicitly set the node order yourself - though it should be possible to visit all running Ndefs, figure out the dependency graph based on their inputs, and then programmatically determine the right node order?

I gave up on this workflow in the end, because it was too prone to random breakages and unpredictable node ordering.

Thanks a lot @scztt for the detailed response.

It also shows how useful your NodeSnapshot quark can be !

When you write : “I gave up on this workflow in the end, because it was too prone to random breakages and unpredictable node ordering”, do you mean you gave up on using the <<> operator, or you gave up using Ndef or NodeProxy at all ? I’ve just started using them :slight_smile:

All the best !


Well, specifically I meant the <<> operator.

Truth be told, I’ve mostly moved away from Ndef / NodeProxy as well. I drive almost everything using patterns these days - I find that Ndef doesn’t play very well with Pdef, and there are node order and message synchronization issues that can make Ndef very hard to use with a signal chain that’s dynamic. Ndef remains great for setups where you have a bunch of long-lived synths that don’t have a lot of dependencies.

Thanks for clarifying ! It’s always good to know where the pitfalls and caveats can be when you start a journey !

That’s strange. Node order shouldn’t matter for synth mapping. It seems that something else is wrong.

Here is a fix:

Also I’ve added a PR for a test that makes sure that node order doesn’t matter:

@scztt and @Geoffroy maybe you could test it?

Just as a final comment: node order really shouldn’t matter!

At least as long as you don’t need to minimize block delay in the nodes, e.g. because you have external sound input (then indeed you can use orderNodes).

You are, of course, correct :slight_smile: - in mapping cases (which is what <<> is used for), node order doesn’t matter apart from delay. The node order is more of an issue with In.

I spent the last few days working on some Pattern tools for correctly node-ordering Events based on their inter-dependencies, in some sorta complex signal chains / playback scenarios. Now that I’ve built myself a hammer, everything looks like a nail.

Tested, I added some notes to the PR - everything looks good, thanks for fixing this so quickly. I was interested to see that the NodeProxy mapping behavior with elastic is actually much more powerful than I expected, once it’s unblocked by the bug. It seems like any elastic changes, rate or channel count, causes all existing mappings to be updated - very useful behavior.

Tested your PR, and it works for me ! Thanks again for your quick fix, really appreciated !