Default argument values in a function don't evaluate

Hello, I have come across this case that I would like to share with you: when I specify the default value for a function argument algebraically—> it is not evaluated, for example:

// sea waves
(
~sea  = {
	arg num_waves_pertime = 1/5; 		// 1 wave in 5 seconds
	var noise, waves, out;

	noise = PinkNoise.ar(mul: 0.4);
	waves = SinOsc.ar(freq: num_waves_pertime).range(0.2,1);

	out = noise * waves;
}
)

s = ~sea.play; 						 // doesn't work default (1/5) ---> no waves!
s.set(\num_waves_pertime, 1/5);				// now works...

I check that you have to specify the default value of type float for it to work:

~sea  = {
	arg num_waves_pertime = 0.2; 	

Maybe this is not the right way to do it, ¿do you have any workarround for this?

Thanks!

Way from pc right now, but pretty sure this works…

~sea  = {
	var noise = PinkNoise.ar(mul: 0.4);
	var waves = SinOsc.ar(
      freq: \num_waves_pertime.kr(1/5) 
    ).range(0.2,1);

	noise * waves;
}
1 Like

Sadly this is a limitation of the language - @jordan’s solution is the only way.

By way of explanation: when you specify a function for a SynthDef and use arguments as your controls, this is what’s implicitly happening:

  1. You define a SynthDef e.g. SynthDef(\foo, { |a = 10, b = 20| })
  2. When building the synth, sclang looks at the arguments of the function you’ve supplied, turns those into NamedControl’s, and then actually calls the function to produce the final synth graph. You can imagine something like:
controlA = NamedControl(sdFunction.def.args[0], sdFunction.def.prototypeFrame[0], \control);
controlB = NamedControl(sdFunction.def.args[1], sdFunction.def.prototypeFrame[1], \control);
result = sdFunction.value(controlA, controlB);
  1. When you create a function with literal values like 10 as defaults, these are stored in prototypeFrame. However, any default value that requires calculation or code execution - anything that isn’t a literal value - is implicitly turned into something like this:
{ |a = (1/4)| }  // if you write this
{ |a| if (a.isNil) { a = 1/4 } } // what you implicitly get is this

Basically: default values that require code execution are not visible when you’re inspecting the function definition, so the whole “implicitly create controls from the arguments” technique doesn’t work.

There are a handful of gotchas in sclang related to this way of treating default values, AND some gotchas related to this way of creating NamedControls - in this case, these gotchas combine to give you the weird behavior you’re seeing.

1 Like

Thank you very much for the analysis and the detailed explanation, it has clarified to a great extent the details of the architecture. The workarround proposed by @jordan is quite satisfactory for me, in fact I had already begun to appreciate the advantages of defining the arguments inline by accessing the .kr method of the variables. I believe it makes coding more dynamic and clean.