Troubleshooting: numWireBufs & NamedControls problem

For fun… here’s a small scale, concrete demonstration of the weird kinds of things that can happen with the topological UGen sort.

Here’s a good case:

(
d = SynthDef(\test, {
	var n = 3;
	var ctls = Array.fill(n, { |i|
		[
			("freq" ++ i).asSymbol.kr(110 * (i+1)),
			("amp" ++ i).asSymbol.kr(1 / (i+1))
		]
	}).flop;  // 'flop' puts all freqs in one subarray, amps in the other
	var sig = SinOsc.ar(ctls[0]) * ctls[1];
	Out.ar(0, sig.sum)
}).dumpUGens;
)

And I’ll annotate the dumpUGens output with two additional columns:

  • For each audio rate unit, how many signals are added and how many are consumed (I’m assuming 1/ every ar UGen requires a wirebuf for its output, so +1 inherently, and 2/ if an ar unit takes an earlier ar unit as an input, and the earlier unit is not used anywhere else, then its wirebuf is released = -1).
  • Total wirebufs in use at that point.
+/- wirebuf wirebufs
[ 0_Control, control, nil ]
+1 1 [ 1_SinOsc, audio, [ 0_Control[0], 0.0 ] ]
[ 2_Control, control, nil ]
[ 3_Control, control, nil ]
+1 2 [ 4_SinOsc, audio, [ 3_Control[0], 0.0 ] ]
[ 5_Control, control, nil ]
+1 -1 2 [ 6_*, audio, [ 4_SinOsc, 5_Control[0] ] ]
+1 -2 1 [ 7_MulAdd, audio, [ 1_SinOsc, 2_Control[0], 6_* ] ]
[ 8_Control, control, nil ]
+1 2 [ 9_SinOsc, audio, [ 8_Control[0], 0.0 ] ]
[ 10_Control, control, nil ]
+1 -2 1 [ 11_MulAdd, audio, [ 9_SinOsc, 10_Control[0], 7_MulAdd ] ]
-1 0 [ 12_Out, audio, [ 0, 11_MulAdd ] ]

The above ordering is ideal: at 7_MulAdd, it collapses two oscillator-multiply chains down into one signal before introducing the third. If n = 10, or 50, or 200, the graph will not get wider because it will always follow this pattern: new chain → collapse, new chain → collapse.

But that way of defining the NamedControls looks clunky. Let’s separate the freqs and amps.

(
d = SynthDef(\test, {
	var n = 3;
	var freqs = Array.fill(n, { |i|
		("freq" ++ i).asSymbol.kr(110 * (i+1))
	});
	var amps = Array.fill(n, { |i|
		("amp" ++ i).asSymbol.kr(1 / (i+1))
	});
	var sines = SinOsc.ar(freqs);
	var sig = sines * amps;
	Out.ar(0, sig.sum)
}).dumpUGens;
)
+/- wirebuf wirebufs
[ 0_Control, control, nil ]
+1 1 [ 1_SinOsc, audio, [ 0_Control[0], 0.0 ] ]
[ 2_Control, control, nil ]
+1 2 [ 3_SinOsc, audio, [ 2_Control[0], 0.0 ] ]
[ 4_Control, control, nil ]
+1 3 [ 5_SinOsc, audio, [ 4_Control[0], 0.0 ] ]
[ 6_Control, control, nil ]
[ 7_Control, control, nil ]
+1 -1 3 [ 8_*, audio, [ 3_SinOsc, 7_Control[0] ] ]
+1 -2 2 [ 9_MulAdd, audio, [ 1_SinOsc, 6_Control[0], 8_* ] ]
[ 10_Control, control, nil ]
+1 -2 1 [ 11_MulAdd, audio, [ 5_SinOsc, 10_Control[0], 9_MulAdd ] ]
-1 0 [ 12_Out, audio, [ 0, 11_MulAdd ] ]

… and all 3 SinOscs have clumped up to the top. So, right at the start, you will need at least n wirebufs, where the other SynthDef could increase n arbitrarily. (Edit: Arrayed NamedControls produce the same structure.)

So a small change in the manner of defining the inputs produced a radically different (and suboptimal) structure.

FWIW, my alternate sort handles both of the above SynthDefs identically – but, if you Splay.ar (that is, Pan2 each change into a stereo pair before summing), then the original sort works slightly better than the alternate! … Which I take to mean that, no matter what we do, there will always be some case that is not handled perfectly. What’s unfortunate about that is that it’s not at all easy to understand why the UGen sort is behaving as it does, thus, hard to predict the best way to write your code :confused: … just have to try it, and adjust when needed.

hjh

2 Likes