There’s not really a quick way to address that
so…
The #1 goal of SC is glitch-free audio. One of the major takeaways from this excellent article is that glitch-free real-time audio requires strict avoidance of time-unbounded operations in the audio thread. Compiling and optimizing a SynthDef is a complex process and definitely not time-bounded – therefore this shouldn’t be done in the audio thread. SC Server moves compiling/optimizing not only outside the audio thread, but also outside the audio process, for greater stability of the audio stream.
In fact, SuperCollider 2 did compile subgraphs dynamically (Spawn.ar
) but James McCartney felt this was not adequate, because every note would require a build/optimize cycle in the audio thread, raising the risk of CPU overruns. I agree that it isn’t convenient to be unable to write the synth design as you did, but… you really don’t want to lower the threshold for audio glitches. (It seems to be fairly common for incoming users to say “SC should allow this” without realizing that disallowing this is SC’s way to prevent a worse outcome.)
Consider also that enabling and disabling oscillators dynamically requires an envelope on each oscillator – otherwise you will get potentially loud pops as a nonzero signal is suddenly interrupted. So, even if SC supported that synth design as written, it wouldn’t sound good. (Also, if you wanted the oscillators to remain phase-synced, stopping and starting them wouldn’t be an option at all.)
Here’s an phase-synced, enveloped approach:
(
a = { |freq = 100, amp = 0.2|
var maxPartials = 50;
var numPartials = MouseX.kr(1, maxPartials);
var sig = Array.fill(maxPartials, { |i|
var active = i < numPartials; // envelope gate
// 'i.reciprocal' is for sawtooth spectrum
var partialEg = EnvGen.kr(Env.asr(0.05, (i+1).reciprocal, 0.05), active);
SinOsc.ar(freq * (i + 1)) * partialEg
}).sum * amp;
sig.dup
}.play;
)
The level of granularity for adding/removing DSP is the synth node. So, another design to add/remove oscillators dynamically is to make a SynthDef for one oscillator, and create/destroy synth nodes as needed, as the number of oscillators changes. “But that’s crazy, why can’t SynthDef do it internally?” This is often not understood, but SynthDef is actually a fairly low level class – it maps directly onto the server’s concept of a GraphDef. (Similarly, Synth is a low-level class.) These happen to map neatly onto musical notes, so they are broadly useful and are used directly throughout the help. This leads users to infer that SynthDef and Synth should handle all cases directly. I’m of the opinion that they shouldn’t. If SynthDef were extended to support dynamic graphs, we would still need a class to represent the server’s GraphDef! So the more logical way is to build abstractions on top of SynthDef and Synth. JITLib is one set of abstractions. Instr and Patch from crucial-library is another (and this might do some of what you’re after here, in fact).
hjh