SelectX value is late?

OK… I was trying to answer this question and found a strange behavior with SelectX:

a = { |x = 0|
	[x, SelectX.kr(x, (0..3))].poll(Changed.kr(x));
	Silent.ar(1)
}.play;

a.set(\x, 0.5);

UGen Array [0]: 0.5
UGen Array [1]: 0
  • x has updated.
  • Changed.kr has recognized x’s new value and fired a trigger.
  • SelectX is producing the value for the old x.

Am I missing something…? That seems wrong to me.

hjh

OK, I see what the problem is. If I’m not mistaken, it’s a general problem that likely affects many UGens.

A typical pattern for audio-rate units with control-rate inputs is, for the kr inputs:

  • Calculate the slope to interpolate between the previous input value and the next one ((new - old) / numSamples where numSamples is an argument to the function).
  • Set a temporary variable to the previous input value.
  • Then, for each audio sample:
    • Calculate the output sample.
    • temp += slope

So, during the control block in which the input value changed, you will get a ramp for the kr input, which ends at the new value on the next control block.

The problem occurs when a Something_next_ak calculation function is used when all inputs are control rate. Then numSamples == 1, and the “slope” is simply the difference between the new and old values.

In this case, when the calculation loop first produces the output sample, and updates the kr ramp after that, it means that the only output value for the current control block reflects the old kr input value.

That’s terribly counterintuitive. I can’t think of any justification for that behavior.

And the solution is fairly straightforward: invert the operations in the calculation loop. If you do temp += slope first, and calculate the output sample after that, then a/ this kr-only case will work as everyone would expect and b/ the ar-kr case would be only very slightly phase-shifted. (Currently, in the ar-kr case, when the kr value updates, the first sample in that control block will reflect the old value. There’s a strong case to be made that this is a mistake. If I change a kr input, I expect the output value to start changing immediately, not one sample later, even if the final value is reached at or just before the next control block boundary.)

Seemingly innocuous design decision with an undesirable consequence. We should probably fix it, but I dread the prospect of reviewing thousands of calculation functions…

hjh

Confirmed:

Old code reproduces the bug:

        LOOP1(inNumSamples, ZXP(out) = ZXP(leftin) * leftamp + ZXP(rightin) * rightamp; leftamp += leftampslope;
              rightamp += rightampslope;);

New code does not show the bug:

        LOOP1(inNumSamples, leftamp += leftampslope;
              rightamp += rightampslope; ZXP(out) = ZXP(leftin) * leftamp + ZXP(rightin) * rightamp;);

hjh

2 Likes