Newbie: weird integer error (perhaps with Mix?)

Hi folks,

I’m getting a weird error when building a synth, with one of its arguments.

If I give it an initial value when declaring it in the “arg” line, the synth fails.

However, if I give it a value AFTER the “arg” line (nTones = 10 ;), then the synth works.

Could someone please explain WHY this occurs?

I use nTones as an argument to the Mix.fill UG. The error message that comes up is:

ERROR: Primitive ‘_BasicNew’ failed.
Index not an Integer

Here’s the code:

(
SynthDef(\chord,
{
arg freq = 440, amp = 0.5, cents = 50, nTones = 10 ;
var signal, k ;

// nTones = 10 ;  // if this line is commented OUT, if fails. If left in, it works.

k = 1200 / log10(2) ;
signal = Mix.fill(nTones,
	{
		arg i, delta ;
		delta = 10.pow(((i+1) * cents) / k) ;
		(amp/(i+1)) * SinOsc.ar(freq * delta) ;
	}
);

Out.ar(0, signal);
}

).add ;
)

Changing the number of tones changes the SynthDef structure – so this is not in any case allowed to be done by an argument.

So it isn’t a weird error at all. Completely normal.

The SynthDef structure must be fixed at the time of SynthDef building.

What you could do is to create multiple SynthDefs, each with a different size, and then choose the right one for the number of tones. There’s a “mass-producing SynthDefs” example somewhere in the documentation.

hjh

thanks very much, I appreciate the clarity of your response.

Still, I find it a weakness of the language — the function is not creating N tones, it’s creating one tone, which happens to have N partials (even though I did name the variable nTones).

but thanks very much, anyway.

Arun

perhaps try a function instead of a SynthDef.

this function returns an array of synths

~tones = { 
  arg nTones;
  nTones.collect{ 
    {SinOsc.ar(rrand(300, 500), 0, 0.05)}.play 
  }
};

~tones.(6);

Thank you!

I’ll try that.

Arun

There’s not really a quick way to address that :laughing: 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

Thanks for your extensive answer.

I’m a composer, so I use the software for non-realtime synthesis.

I guess I don’t understand the logic of needing to add/remove an oscillator in order to build each partial of the sound. Why can’t the oscillator be added, use for as many times as needed, and then, when its use is ended, THEN it’s removed?

then the allocation and deallocation would only have to happen once, as I understand it.

I’ll also look up the article by Ross Bencina that you refer to, perhaps that will help me understand what’s going on.

Arun

That’s a very good question – in fact, my code example does allocate oscillators once and only once, using the envelopes to suppress the signal when an oscillator is inactive.

I got onto the topic of adding and removing the units because that’s what your original code snippet literally means – that the actual array size should be under control of a server-side modulation signal. Your example would allow something like this:

a = Synth(\chord, [nTones: 10]);

a.set(\nTones, 20);

a.set(\nTones, 5);

… and the usage of nTones would imply that the size of the Mix.fill should increase and decrease accordingly. As written, nTones is modulatable – therefore the server would have to support modulating the size of the SynthDef structure – and modulating the size of the structure does actually mean adding or removing units. (“But I don’t want to change it after it starts, only once per synth” – I can understand that, but, to set the size at synth onset would create the same problem for the server – the server would have to adapt the literal size of the graph.)

If, on the other hand, what you’re interested in is to hear only ‘n’ partials, where ‘n’ can be freely modulated, that is already possible by using ‘n’ to open and close envelopes applied to continuously-running oscillators – my example above.

I think it just requires a shift of perspective on the problem. Putting nTones into Mix.fill just isn’t going to work with the SC Server design. But there are other ways to get there: 1/ enveloping oscillators; 2/ making multiple SynthDefs with different sizes; 3/ one synth per partial; 4/ probably some others I can’t think of right now.

hjh

I think this is a very important point about SC generally speaking – the language gives you a particular set of relatively low-level abstractions on top of the server’s OSC interface, but also gives you the means to create and compose further abstractions that may be better suited to your compositional goals.

Instr, Ndef, etc are not only useful in themselves but also as examples of the kind of things that can be built on top of Synth and SynthDef.

Further to this idea, here’s an example of how you might use a higher order Function to construct the graph Function for your SynthDef:

(
    s.waitForBoot({
        // define a Function that returns a graph Function with the required number of SinOscs
        ~makeGraphFunc = {|nTones|
            {|freq=440, amp=0.5, cents=50|
                var signal, k;

                k = 1200 / log10(2);

                signal = Mix.fill(nTones, {|i|
                    var delta;

                    delta = 10.pow(((i + 1) * cents) / k);
                    (amp / (i + 1)) * SinOsc.ar(freq * delta)
                });

                Out.ar(0, signal)
            }
        };

        // build and add a SynthDef
        SynthDef.new(\chord, ~makeGraphFunc.value(10)).add;
        s.sync;

        // create a Synth
        ~synth = Synth.new(\chord)
    })
)