Can't index an array in a SynthDef using an argument?

My overall goal here is to provide a synth a collection of values in one argument, and the most straightforward way I could think of was to provide an index to a globally available list that contains several arrays of values. In the future it would probably make sense to just directly provide an instance of a class I would write, but this issue is making me think I’m severely overestimating SynthDefs.

I think there must be some rule about SynthDef arguments that I’m missing, most likely related to the idea that SynthDefs must be a defined size when they are interpreted (which I learned the hard way a few hours ago).

Anyway here’s the code block, just an example, but I don’t have a clue why it doesn’t work:

SynthDef(\test, {
	arg int = 1;
	var array = #[0,1,2,3];
	array.at(int.asInteger).postln;
});

The error I’m getting is “Index not an Integer”. Full error at this link

Additional information:

  • The function inside the SynthDef works perfectly as a standalone function
  • the argument is typed as an OutputProxy, and after asInteger is typed as a UnaryOpUGen

I’d be grateful for any pointers here, and especially grateful for documentation targetting this topic that I could read through. Thanks!

See Select.kr.

hjh

That fixed it! Thank you!

So what I’m guessing from this is that arguments are supposed to be used in UGens, and not treated the same as a literal? Standard indexing is evaluated when the SynthDef is created, but a UGen is evaluated when a Synth is created?

If that’s the case, if I wanted to use a class as an argument I would have to make a UGen that accepts that custom class, which sounds like too much of a pain to be worth it right now.

A SynthDef argument is always an OutputProxy representing one channel of a Control UGen (or an array of these). This is because it represents a value that may change at any time. In signal-processing-land, a changeable value must be a signal. Signals may change; numbers don’t. So, even though you wrote arg int = 1, int is not actually 1. It’s a control rate signal whose value will be 1.0 (samples are always floating point in the server – there are no integer samples) until a different value overrides it.

Array indexing by at is a language-side operation. The language doesn’t have access to server-side signals. Therefore indexing an array by a signal is illegal.

You can, instead, use a UGen whose signal output is the result of indexing an array of signals (i.e. Select).

For the reasons discussed above, you cannot pass an arbitrary object as an argument to a SynthDef.

The arguments are signals.

Signals represent sample values, which must be numeric.

So the arguments (both the defaults in the SynthDef, and the values in Synth and .set statements) must be numeric.

You can pass arbitrary objects to a function that builds a SynthDef, but that would influence the SynthDef structure only.

hjh

1 Like

If that’s the case, if I wanted to use a class as an argument I would have to make a UGen that accepts that custom class, which sounds like too much of a pain to be worth it right now.

It’s even worse: arguments to Synths or Ugens can only be floating point numbers, or arrays of floating point numbers - so you can’t straightforwardly pass a class in as an argument either. BUT - it’s actually possible to get comparable behavior pretty easily, as long as your class can be boiled down to an array of numbers :slight_smile:

  1. In 95% of cases, .asControlInput is called on any object passed in as an argument to a synth.
  2. Classes can be used in a SynthDef, as long as you understand that any code that is executed is not running in the Synth, per se, but only defining it’s structure.

You can still encapsulate complex arguments + functionality into a class. For example:

EQCurve {
     var freqLow, dbLow, freqHi, dbHi;

     *new {  ^super.newCopyArgs(freqLow, dbLow, freqHi, dbHi) }
     *default { ^this.new(440, 0, 880, 0) }
     *fromArray { |array| ^this.new(*array) }
     *fromKrArg { 
           |name| 
           var synthArg = name.asSymbol.kr(this.default.asArray); // a NamedControl
           ^EQCurve.fromArray(synthArg);
      }

     asArray { ^[freqLow, dbLow, freqHi, dbHi] }
     asControlInput { ^this.asArray }
     ar {
         |sig, res|
         sig = BLowShelf.ar(sig, freqLow, res, dbLow);
         sig = BHiShelf.ar(sig, freqHi, res, dbHi);
     }
}

Now, you have an object that automatically gets converted to the right kind of array when you pass it as a synth arg, and can also construct itself from a named synth argument. The usage would be something like:

~synth = {
     var eq = EQCurve.fromKrArg(\eq);
     var sig = WhiteNoise.ar();
     eq.ar(sig, 0.3)  
}.play;

~synth.set(\eq, EQCurve(100, 500, -3, 5);

(I just quickly wrote up this code and didn’t test it, this is mostly just to point in the right direction)

wow so helpful thanks! Off to litter .asControlInput methods around…

there might be an opportunity for an error message that gives guidance when ppl ry to use inappropriate inputs in a SynthDef - seems like a frequently encountered confusion

Here would be the place: https://github.com/supercollider/supercollider/blob/develop/SCClassLibrary/Common/Core/Object.sc#L713 – there’s some risk of breakage though; throwing an error here would need extensive testing.

The division between language and server is confusing for a lot of users (that you have an object in the language but the server can’t automatically use it, or a signal in the server but the language can’t immediately access it). It’s just one of the things that users eventually have to confront (no way around it), and until confronting it, it’s going to seem weird.

James McCartney did say at least once that, if he were doing it again, he would disallow SynthDef arguments and require synth inputs to be written as explicit Control UGens – for exactly the reason in this thread: the fact that they are arguments leads to an expectation that you can use them just like normal function arguments, but they aren’t normal function arguments. (Edit: related to that, see NamedControl: a better way to write SynthDef arguments – I still like and use SynthDef arguments, but if everyone used NamedControls like this, maybe the question in this thread would have been clearer.)

hjh