Default synthdef makes zombie nodes

Running e.g.

(dur: 0.0001).play

at the default block size results in a non-sounding but omnipresent synth node. I feel this is not a great thing.

(especially since a beginner experimenting with language-side granular synthesis might quickly run into it and have no idea why at a certain point their patch devolves into FAILURE IN SERVER /s_new too many nodes messages)

It can be sort of fixed by adding an impulse to the gate, which guarantees that the envelope will always fire and therefore the synth will always end:

SynthDef(\default, { arg out=0, freq=440, amp=0.1, pan=0, gate=1;
  var z;
  gate = gate + Impulse.kr(0);
  z = LPF.ar(
    Mix.new(VarSaw.ar(freq + [0, Rand(-0.4,0.0), Rand(0.0,0.4)], 0, 0.3, 0.3)),
    XLine.kr(Rand(4000,5000), Rand(2500,3200), 1)
  ) * Linen.kr(gate, 0.01, 0.7, 0.3, 2);
  OffsetOut.ar(out, Pan2.ar(z, pan, amp));
}, [\ir]).add;

This would probably be considered a breaking change since it could change the sound of existing code… Is there a better way?

I don’t strongly object to this change in the class library, but I don’t necessarily think it really solves the problem, so I’m not endorsing it either.

FWIW I’m not entirely convinced by the granular use case, because I’d guess that a majority of the time, users experimenting with language-side granular synthesis will be using a SynthDef other than \default, in which case they’ll need to understand about the behavior of gate synth inputs anyway. (E.g., “I tried a couple patterns with \default and it was fine, but when I wrote my own SynthDef, I got this pileup.”)

For good or for ill, it isn’t possible to learn any programming environment without making mistakes. It’s valid to want to smooth over rough edges where there are weaknesses in the programming interface, but here, part of the problem may be that the meaning of a 0.0001-beat synth might not have been fully thought through. Such as, the attack segment of the Linen is 10 ms and gate would be open for 0.1 ms or 5 samples, so it won’t get above 0.01 or -40 dB (and the sustain level is 0.7, so the actual peak would be likely below this)… “Why is it so quiet?” Or, typical grain rate is less than 100-200 grains per second but dur: 0.0001 suggests 10000 per second (where you may run into the max number of nodes). ((dur: 0.008, sustain: 0.0001) would be a reasonable rate, with low overlap to reproduce the problem.) For granular, does it need to be a gated envelope? Etc etc.

So there’s part of me that sees this as being similar to an infinite loop problem. It’s easy to bump into it accidentally (I forgot to increment a variable in a while just the other day kaboom, and I’ve been using SC for a couple decades), and the root cause of the problem is that the code is asking for something not quite right – and at some point, eventually, one will just have to fix the code.

hjh

1 Like

The lesson is that envelopes need to be audio rate for their doneActions to be able to end events of duration less than the block size… So Env.linen(0.01, 0.7, 0.3, 2).ar(2, gate) would be more instructive perhaps (since Linen doesn’t have an *ar method! grrr)

Not exactly, because the gate input is still control rate. If you open the gate in the s_new message and close it within the same control block, it’s the same as if it hadn’t been opened at all.

hjh

are you sure? this does play on my system:

SynthDef(\default, { arg out=0, freq=440, amp=0.1, pan=0, gate=1;
  var z;
  gate = gate + Impulse.kr(0);
  z = LPF.ar(
    Mix.new(VarSaw.ar(freq + [0, Rand(-0.4,0.0), Rand(0.0,0.4)], 0, 0.3, 0.3)),
    XLine.kr(Rand(4000,5000), Rand(2500,3200), 1)
  ) * K2A.ar( Linen.kr(gate, 0.01, 0.7, 0.3, 2) );
  OffsetOut.ar(out, Pan2.ar(z, pan, amp));
}, [\ir]).add;

(dur: 0.0001).play

Remove the Impulse here and you’ll see the problem. Also the Linen is still kr! K2A does not make the underlying UGen audio rate.

SynthDef(\default, { arg out=0, freq=440, amp=0.1, pan=0, gate=1;
	var z;
	z = LPF.ar(
		Mix.new(VarSaw.ar(freq + [0, Rand(-0.4,0.0), Rand(0.0,0.4)], 0, 0.3, 0.3)),
		XLine.kr(Rand(4000,5000), Rand(2500,3200), 1)
	) * EnvGen.ar(Env.asr(0.01, 0.7, 0.3), gate, doneAction: 2);
	OffsetOut.ar(out, Pan2.ar(z, pan, amp));
}, [\ir]).add;

(dur: 0.0001).play  // stuck node i.e. simply using an 'ar' envelope doesn't fix it

hjh

1 Like

I added the line gate = gate + Impulse.kr(0) to make sure it plays. The current default synthdef does not have this line, and (dur: 0.0001).play does not sound but leaves a synth node running on the server forever.

I stumbled across this looking at the “recursive_phrasing” helpfile and trying to understand the \recursionLevel key. So I built my own example using the default synthdef, something like this:

(
Pdef(\gest, { |sustain = 1, n = 8, freq = 440, ratio = 0.1|
  Pbind(
    \dur, sustain.value / n,
    \freq, Pseq((1..n)) * ratio + 1 * freq.value
  )
});
Pbind(
  \type, \phrase,
  \instrument, \gest,
  \octave, 2,
  \note, Prand([0, 7, 12], inf),
  \ratio, Pwhite(0.01, 1.0),
  \dur, Pseq([2, 1, 0.5], inf),
  \recursionLevel, 0
).play;
)

all made sense, same with \recursionLevel, 1, but then it breaks down with \recursionLevel, 2 because some of the sustains are too short.

I knew what the problem was because I’ve run into it before with my own synthdefs, confirmed by pulling up the node tree and seeing the zombie nodes, and I knew to search the class library for SynthDef(\default to find where it is defined – but I suspect a new user would just think that this many simultaneous sounding notes is too much for SC, which isn’t the case.

By tracing I see that the Pdef’s events are getting down to \dur, 0.002 – was that intended? 500 events per second.

The broader question is, how far should SC go to prevent one from shooting oneself in the foot? Because this does look to me like a case of shooting oneself in the foot – where (perhaps) unexpected data are being produced and not fully controlled. True, new users won’t be aware of tracing and debug-posting methods to investigate – but at the end of the day, it isn’t SC’s responsibility to override extreme data.

hjh

1 Like

You’re right, this example is totally shooting oneself in the foot – although using code not very different from that found in a helpfile (which even goes up to recursionLevel, 3)

Maybe the real issue for me is that the error message FAILURE IN SERVER /s_new too many nodes in this case gives poor information about what is going on… but it’s not clear to me how to improve it since all the server knows is that there are indeed too many nodes.