fadeTime and use of `XOut` in NodeProxy \filter role

Assuming the following minimal ndef setup:

Ndef(\a).fadeTime = 10;
Ndef(\a)[0] = {PinkNoise.ar!2};
Ndef(\a)[1] = \filter -> {|in| [in[0], Silent.ar]};
Ndef(\a).scope;

when re-evaluating the filter line, the input bleeds through the right channel (see scope).

Ndef(\a)[1] = \filterWet -> {|in| [in[0], Silent.ar]}

I tracked this down to be because of the use of XOut in the filter pattern (we’d only want to crossfade between the two \filter instances, not the previous signal).

Any help much appreciated.

Even though here it clearly is not, it was intended as intended behaviour.

When you fade out a filter (A → B), you want this to be a continuous transformation to indentity: (A → B) → (A → A).

In this case this means (where Silent is 0):

(A → 0) → (A → A).

So here you would obviously like to have:

(A → 0) → (A → 0).

This means not a fade out, but a direct cross fade to the new filter output

(A → B1) → (A → B2)

We would need to distinguish two cases: a cross fade and a fade out. Currently we have no way to know this.

Written directly, you want something like this (ignoring fade):

Ndef(\a)[1] = { ReplaceOut.ar(\out.kr, In.ar(\out.kr, 2) * [1, 0]) };

You would have to add a different kind of filter role for this, where you could set a parameter to swithc between the two behaviours.

thanks for looking into this!

Let me make this even more simple:

Ndef(\a).fadeTime = 10;
Ndef(\a)[0] = {PinkNoise.ar};
Ndef(\a)[1] = \filterIn -> {|in| Silent.ar};
Ndef(\a).scope;

when replacing the filter with itself, i.e. repeatedly evaluating

Ndef(\a)[1] = \filterIn -> {|in| Silent.ar};

the original sound comes back in temporarily.

This is especially annoying, if the filter implements panning or (substantial) amplitude modulation in a bigger chain and I change one (unrelated) parameter via xset.

AFAICS, the difficulty here is that the stack that is writing to a single Bus.

The chain is (in this concrete case):

[0] // sound
 v // 1 * [0]
[1] // XOUT(1) filter  
 v // 1 * [1]
bus

when replacing the filter, this happens (time stopped at fadeTime/2):

[0] // sound
 v
[1] // XOUT(0.5) filter (old)      
 v     /// here, we have: 0.5 * [0] + 0.5 * [1]
[1'] // XOUT(0.5) filter (new)
 v     /// here, we have: 0.25 * [0] + 0.25 * [1] + 0.5 * [1']
bus

which effectively means that we do not have any way to get to the original signal
We would like to have this, though:

[0] // sound
 | \
 | [1] // XOUT(0.5) filter (old)      
 |  |
 |  v
 | bus 1
 \   
 [1'] // XOUT(0.5) filter (new)
   |
   v
  bus 2

SUM (bus1, bus2)

this mimics the current behaviour nicely:

(
Ndef(\a)[0] = {PinkNoise.ar};
Ndef(\a)[1] = { 
	var time = \time.kr(0, spec: [0, 1]);
	var env = IEnvGen.kr(Env.sine, (1 + time) * 0.5); // old filter, time increases >> envelope becomes 0	
	var in = In.ar(\out.kr, 2);
	var func = SinOsc.ar(200) * LFPulse.ar(10);
	XOut.ar(\out.kr, env, func) 
};
Ndef(\a)[2] = { 
	var time = \time.kr(0, spec: [0, 1]);
	var env = IEnvGen.kr(Env.sine, time * 0.5); // new filter, time increases >> envelope becomes 1	
	var in = In.ar(\out.kr, 2);
	var func = SinOsc.ar(400) * LFPulse.ar(5);
	XOut.ar(\out.kr, env, func) 
};
)

Ndef(\a).set(\time, 0); // only old filter
Ndef(\a).set(\time, 0.5); // weird mix
Ndef(\a).set(\time, 1); // only new filter

Yes, I always thought I should never have allowed more than one object in a node proxy. It was a nice to have, and has found a lot of use. But eventually, it is a syntactic shortcut for something we perhaps should be doing with whole proxyspaces.

There could be a special object that carries this extra bus and is instantiated by an additional role.

I guess it’s always a question in cases like this, whether the object that is almost meeting the requirement should adapt to handle the new scenario, or whether the object that is almost meeting the requirement should be a component of a new superstructure designed for the new job. In SC-land, we often default to the position that objects should do more stuff; I’m not sure that’s always right.

Here, if effects processors need their own buses, that may be outside the scope of single-bus-range NodeProxies, so if it were my project, I might consider an object that could coordinate multiple NodeProxies rather than an extension to NodeProxy itself.

hjh