Environment variables inside SynthDef

Hi all,

I’ve known about the following pitfall for a long time, and seen many students fall into it. Essentially, it involves using environment (“tilde”) variables as default values for declared SynthDef arguments. I’ve never been able to provide a precise explanation for why it fails. I’m wondering if someone else can:

s.boot;

~freq = 400; //initialize

(
SynthDef.new(\t, {
	arg freq = ~freq; //seems sensible enough
	var sig; 
	sig = SinOsc.ar(freq, 0, 0.2!2);
	Out.ar(0, sig);
}).add;
)

Synth(\t); //Control UGen for freq defaults to zero

Eli

1 Like

It comes from the way the SynthDefGraph is built from the UGenGraphFunction and especially how the args are processed. You could snoop the SynthDef methods buildUgenGraph and addControlsFromArgsOfFunc and probably track it down there (admittedly I haven’t done that myself now).

If someone wants to use envir variables like that, there’s still a way:

(
SynthDef.new(\t, {
	var sig, freq = \freq.kr(~freq); 
	// or NamedControl.kr(\freq, ~freq); 
	sig = SinOsc.ar(freq, 0, 0.2!2);
	Out.ar(0, sig);
}).add;
)

One might debate if taking over envir variables as default args is good practice or not. I think, on occasion, there are good reasons to do such – and, similarily, to hardwire envir variables within the SynthDef code. E.g. I’m quite often doing so with Buffers and Busses, because the “clean” way to pass them as args often causes more hassle when it comes to the use of GUIs. Also, envir variables are highlighted, which makes them visible within SynthDef code – IMO better to use envir variables in such a way than interpreter variables which are easy to overlook.

As a side-note, you probably know, things are a bit different with Function args

// works

f = { arg x = ~freq; x * 2 };
f.();

// doesn't work

f = { |x = ~freq| x * 2 };

// works

f = { |x = (~freq)| x * 2 };
f.();

I think it’s not a long time ago the design of SynthDef was discussed again in some thread, it might indeed confuse students that args in a SynthDef’s UGenGraphFunction are different from normal Function args in a rather subtle way.

2 Likes

See also:

hjh

Thanks Daniel for the clarifications, and thanks James for the link to this well-written NamedControl tutorial. In fact, I had been looking for this post recently, because I saw it once awhile back and appreciated how useful it was. It reminds me that I ought to make the switch myself, and maybe even make a short tutorial about it. Though I admit, instinctively typing ‘arg’ at the top of a UGen function is going to be a hard habit to kick…

I had a feeling this pitfall was related to the complexities of the SynthDef building process. I agree that using envir variables should not be immediately dismissed as “bad practice.” I don’t use them for busses and buffers, but I do use them for numChannels arguments in UGens that expect them (mostly PanAz), since they can’t be modulated anyway. I have a few pieces designed for presentation on n-channel loudspeaker ring, with an envir variable at the top of the code that I set before running the code.

Thanks again.

Eli

That’s a use case I have forgotten – I’m doing exactly the same :slight_smile: