Fractions in SynthDef Argument

I made a synth with the low pass filter controlled by a triangle LFO. It took me a solid 20 minutes to figure out that what I thought was a problem with using .exprange on the output of the LFO was actually a problem with the SynthDef arguments.

I was thinking in CPM, so naturally
440 cycles / 60 seconds → |modFreq = 440/60|
but this doesn’t work and I’m wondering why using a fraction wouldn’t work as a SynthDef argument on compile. It works with Synth.(\key).set but not as a default.

(

//LFO freq == 440 cpm; modFreq arg is unused in function

SynthDef(\filtSaw, {	
	arg amp=1, pan=0, freq=60, detun=0.5, fFreqLo=20, fFreqHi=1500, modFreq=440/60, dur=5;
	var sig, env, filtMod;
	
	filtMod = LFTri.ar(22/3, 0.0).exprange(fFreqLo,fFreqHi);
	
	env = EnvGen.ar(Env.triangle(dur), doneAction:2);
	sig = 7.collect({
		LFSaw.ar(freq*rand2(detun).midiratio, 4.0.rand)/7;
	});
	sig = Mix.ar(sig);
	sig = RLPF.ar(sig, filtMod, 0.3);
	sig = Pan2.ar(sig, pan);
	sig = sig * amp * env;
	Out.ar(0, sig);
}).add;



//LFO freq == \modFreq == 440/60

SynthDef(\filtSaw1, {
	arg amp=1, pan=0, freq=60, detun=0.5, fFreqLo=20, fFreqHi=1500, modFreq=440/60, dur=5;
	var sig, env, filtMod;
	filtMod = LFTri.ar(modFreq, 0.0).exprange(fFreqLo,fFreqHi);	
	env = EnvGen.ar(Env.triangle(dur), doneAction:2);
	sig = 7.collect({
		LFSaw.ar(freq*rand2(detun).midiratio, 4.0.rand)/7;
	});
	sig = Mix.ar(sig);
	sig = RLPF.ar(sig, filtMod, 0.3);
	sig = Pan2.ar(sig, pan);
	sig = sig * amp * env;
	Out.ar(0, sig);
}).add;



//modFreq as a variable

SynthDef(\filtSaw2, {	
	arg amp=1, pan=0, freq=60, detun=0.5, fFreqLo=20, fFreqHi=1500, dur=5;
	var sig, env, filtMod, modFreq=440/60;
	
	filtMod = LFTri.ar(22/3, 0.0).exprange(fFreqLo,fFreqHi);
	
	env = EnvGen.ar(Env.triangle(dur), doneAction:2);
	sig = 7.collect({
		LFSaw.ar(freq*rand2(detun).midiratio, 4.0.rand)/7;
	});
	sig = Mix.ar(sig);
	sig = RLPF.ar(sig, filtMod, 0.3);
	sig = Pan2.ar(sig, pan);
	sig = sig * amp * env;
	Out.ar(0, sig);
}).add;



//impractical for this purpose but works

SynthDef(\filtSaw3, {	
	arg amp=1, pan=0, freq=60, detun=0.5, fFreqLo=20, fFreqHi=1500, dur=5, modFreq=7.333333;
	var sig, env, filtMod;
	
	filtMod = LFTri.ar(modFreq, 0.0).exprange(fFreqLo,fFreqHi);
	
	env = EnvGen.ar(Env.triangle(dur), doneAction:2);
	sig = 7.collect({
		LFSaw.ar(freq*rand2(detun).midiratio, 4.0.rand)/7;
	});
	sig = Mix.ar(sig);
	sig = RLPF.ar(sig, filtMod, 0.3);
	sig = Pan2.ar(sig, pan);
	sig = sig * amp * env;
	Out.ar(0, sig);
}).add;


//best solution
SynthDef(\filtSaw4, {	
	arg amp=1, pan=0, freq=60, detun=0.5, fFreqLo=20, fFreqHi=1500, dur=5, modCpm=440;
	var sig, env, filtMod, modFreq;
	
	modFreq = modCpm/60;
	
	filtMod = LFTri.ar(modFreq, 0.0).exprange(fFreqLo,fFreqHi);
	
	env = EnvGen.ar(Env.triangle(dur), doneAction:2);
	sig = 7.collect({
		LFSaw.ar(freq*rand2(detun).midiratio, 4.0.rand)/7;
	});
	sig = Mix.ar(sig);
	sig = RLPF.ar(sig, filtMod, 0.3);
	sig = Pan2.ar(sig, pan);
	sig = sig * amp * env;
	Out.ar(0, sig);
}).add;
)

Synth(\filtSaw); //expected result
Synth(\filtSaw1); //|modFreq = 440/60| doesn't work...
Synth(\filtSaw1).set(\modFreq, 440/60); //magic! now it works
Synth(\filtSaw2); //bad temporary solution
Synth(\filtSaw3); //annoying but works
Synth(\filtSaw4); //best solution probably?

Hi,

A quirk of sclang - if you use pipe syntax, you have to surround an expression with parenthases, so |modFreq = (440/60)|

See Functions | SuperCollider 3.12.2 Help

Edit: OH but actually this might still not work in a synthdef function (not at computer now, sorry) – in any case check out NamedControls for fewer surprises overall: NamedControl: a better way to write SynthDef arguments

1 Like

I did try the parenthesis. Fails both in pipe syntax and in whatever my typical

arg ...
var...

would be called. I’ve seen a few folks posting using the named control. I did look over the documentation for Control which touches on the link you posted. I tried that with the fraction and it does work.

However, perhaps I misunderstood this, it did say “Generally you do not create controls yourself,” which I somehow interpreted as “named control isn’t something you normally would want to do or is deprecated.”

I assume now it’s saying something more along the lines of “Generally you do not use Control.kr/Control.ar directly” and I’ve just misunderstood this.

So now if I’m correct, 7.333333 is now an instance of control and not a float here and a fraction cannot be a control?

It needs to be a literal, i.e.

{ arg x = 1/2, y = 0.5; }.def.prototypeFrame == [nil, 0.5]

1 Like

Actually Control is not the same thing as NamedControl, they’re two different classes. NamedControl is a more recent addition.

Control is tricky to use by yourself – I’d avoid it.

NamedControl is a user-friendly factory for Control UGens. It’s definitely supported/recommended.

If you need to create a synth input where the default is not a literal, the best formula is one of the following:

var x = NamedControl.kr(\x, default_expression);

// or, equivalent:
var x = \x.kr(default_expression);

Note however that the default expression cannot use any UGens – the default must be a fixed number or array.

hjh

1 Like

Thanks for clarifying! I thought what I was seeing with NamedControl was just a syntax for Control. That makes a lot of sense