20.do{} vs. foo.do{}

Hi All,

Very exited about my forst post here :slightly_smiling_face:
I’m kind of new to supercollider so bare with me.
I want to do a simple iteration that layers various versions of a wave.
This code works fine:

(
SynthDef(\multipad, {
	arg freq=100;
	var temp, sum;
	sum = 0;
	10.do{
		temp = VarSaw.ar(freq * {Rand(0.98, 1.02)}!2, {Rand(0.0, 1.0)}!2, 0.5, 0.08);
		sum = sum + temp;
	};
	Out.ar(0, sum);
}).add;
)

x = Synth.new(\multipad);
x.free;

However when I change the Synth to accept a ‘layers’ argument for the amount of iterations, the program runs, but no iterations are made. This is the code:

(
SynthDef(\multipad, {
	arg freq=100, layers=20;
	var temp, sum;
	sum = 0;
	layers.do{
		temp = VarSaw.ar(freq * {Rand(0.98, 1.02)}!2, {Rand(0.0, 1.0)}!2, 0.5, 0.08,);
		sum = sum + temp;
	};
	Out.ar(0, sum);
}).add;
)

x = Synth.new(\multipad);
x.free;

Any help in the right directions would be very much appreciated :blush:

Have a nice Sunday,
Boris

You can’t do in a SynthDef. I mean you can, but it only runs at SynthDef creation time, not on every audio frame. Your 10.do code only runs once, when the SynthDef is created. From the help page:

It is important to understand that although a single def can provide a great deal of flexibility through its arguments, etc., it is nevertheless a static entity. A def’s UGen graph function (and the SC code within it) is evaluated only when the def is created. Thus statements like while, do, collect etc. will have no further effect at the time the def is used to create a Synth, and it is important to understand that a UGen graph function should not be designed in the same way as functions in the language, where multiple evaluations can yield different results. It will be evaluated once and only once.

Try this to see what you’re really do-ing:

(
SynthDef(\multipadNope, {
	arg freq=100, layers=10;
	var temp, sum;
	sum = 0;
	layers.do{
		("cycle!" + layers).postln;
		temp = VarSaw.ar(freq * {Rand(0.98, 1.02)}!2, {Rand(0.0, 1.0)}!2, 0.5, 0.08,);
		sum = sum + temp;
	};
	Out.ar(0, sum);
}).add;
)

It will print cycle! an OutputProxy when you just add the synth.

(In CSound you can force a “synth” to reinit, but there’s no such facility in SC, as far as I know. Likewise for a “synth” recursively spawning copies of itself.)

1 Like

Typical solution (without fixing indentation):

(
~makeSynthDef = { arg layers = 20;
SynthDef(("multipad" ++ layers).asSymbol, {
	arg freq=100;
	var temp, sum;
	sum = 0;
	layers.do {
		temp = VarSaw.ar(freq * {Rand(0.98, 1.02)}!2, {Rand(0.0, 1.0)}!2, 0.5, 0.08,);
		sum = sum + temp;
	};
	Out.ar(0, sum);
}).add;
};
)

You can’t have a SynthDef where the structure depends on the value of a SynthDef argument.

You can create a SynthDef within a function, where the structure depends on the value of a function argument.

SynthDef and function arguments are syntactically similar but their meanings are very very different (as Rfluff correctly explained). You can’t assume them to be fully compatible.

hjh

2 Likes

Well, the exact solution depends on how one wants these number-of-layers changes to occur. If it’s ok to tear up all the previous layers as in: this note has 5 layers, the next note has 10, a simple wrapper like that is enough. For this particular example of additive synthesis, strummed events work just as well:

i = { VarSaw.ar(\freq.kr(100) * Rand(0.98, 1.02 ! 2), Rand(0.0, 1.0 ! 2), 0.5, 0.08) }
(instrument: { EnvGate(fadeTime: 1) * i.() }, dur: 2 ! 5, freq: 140).play
(instrument: { EnvGate(fadeTime: 1) * i.() }, dur: 2 ! 10, freq: 200).play
(instrument: { EnvGate(fadeTime: 1) * i.() }, dur: 2 ! 40, freq: 240).play

f = { rrand (100, 300) }
(instrument: { EnvGate(fadeTime: 1) * i.() }, dur: 2, freq: f ! 5).play
(instrument: { EnvGate(fadeTime: 1) * i.() }, dur: 2, freq: f ! 10).play
(instrument: { EnvGate(fadeTime: 1) * i.() }, dur: 2, freq: f ! 20).play

If one wants to change the polyphony “level” as notes/nodes are playing, without tearing up the the ones already playing, it gets a bit more complicated. Adding new layers is fairly trivial by piling on more events/nodes, but removing some means tracking the ids/nodes and releasing them.

(
fork { 
	e = (type: \on, instrument: { EnvGate(fadeTime: 0.2) * i.() }, freq: f ! 12).play;
	e[\id].do { |x| 0.75.wait; Node.basicNew(s, x).release } 
}
)

(Instead of Node.basicNew...release you can also write (type: \off, id: x).play here.)

But in general, if the do iteration doesn’t have an equivalent in server commands (like it did here for additive synthesis), and you still want a dynamic knob for it, there’s no workaround for writing your own UGen, which alas means going “out of SC language” to C++, Faust, or even Nim.

1 Like

If you don’t mind wasting a bit of calculations for layers you don’t hear, you can also do the following:

(
SynthDef(\multipad, {
	arg freq=100, layers=20;
	var maxLayers = 30;
	var temp, sum;
	sum = 0;
	maxLayers.do { |i|
		temp = VarSaw.ar(freq * {Rand(0.98, 1.02)}!2, {Rand(0.0, 1.0)}!2, 0.5, 0.08,);
		sum = sum + (temp * (layers > i));
	};
	Out.ar(0, sum);
}).add;
)

x = Synth.new(\multipad);
x.set(\layers, 1);
x.set(\layers, 0);
x.set(\layers, 4);

x.free;



(
SynthDef(\multipad, {
	arg freq=100, layers=20;
	var maxLayers = 30;
	var temp, sum;
	sum = 0;
	maxLayers.do { |i|
		temp = VarSaw.ar(freq * {Rand(0.98, 1.02)}!2, {Rand(0.0, 1.0)}!2, 0.5, 0.08,);
		sum = sum + (temp * (layers > i).lag(0.1)); // <---- a bit of lag for smoothing transitions.
	};
	Out.ar(0, sum);
}).add;
)

x = Synth.new(\multipad);
x.set(\layers, 1);
x.set(\layers, 0);
x.set(\layers, 4);

x.free;
2 Likes

Thanks guys for the info!
Lots to learn, lots of fun ahead :wink: