What is the effect of calling lag() on a frequency parameter?

Hi! I am pretty new to SuperCollider, and have been working through Ruviaro’s “A Gentle Introduction to SuperCollider.” I’m finding it a very helpful text, with most things clearly explained.

But there’s one detail that’s puzzling me. There’s a SynthDef example in section 39 that contains the following code:

chorus = freq.lag(2) * LFNoise2.kr([0.4, 0.5, 0.7, 1, 2, 5, 10]).range(1, 1.02);

source = LFSaw.ar(chorus) * 0.5;

What exactly is that call to lag(2) supposed to do? The text doesn’t explain, and the help files also don’t explain what calling lag() on a simple number does. I’ve experimented with different values, from 0 to 18000, and I can’t hear a difference.

hey,

Lag is a OnePole filter, which takes the current value of the input signal and adds it to the prior output value. Often times filter coefficients are calculated in cycles per seconds (hz) for a filter cutoff but they can also be calculated in time (sec) to smooth out control signals.

1 Like

The frequency parameter is expected to be changed here by the user at control rate. If you change a parameter at control rate, its only changing every control block, therefore you get a stepped output signal which can be smoothed over time by filtering. So its anticipating a change over time where lag makes sense, for static values it doesnt.

1 Like

Ohh … kaaay. Just to be sure I’m understanding you, by ‘frequency parameter’, you mean the freq variable - the receiver of the lag() method? Because, as the example is written, that does not seem to be the case. The synth is invoked like this:

a = Array.fill(6, { Synth('wow', [freq: rrand(40, 70).midicps, amp: rrand(0.1, 0.5)]) });

If I’m reading this right, the input frequency is constant for each invocation of the synth, right? So does this mean that it actually doesn’t make sense to do freq.lag(2) in this context?

[BTW, I’ll paste the whole example at the end of this message, in case you don’t have the book handy]

OR … does freq.lag(2) actually produce a signal?

That’s what I mean by asking exactly what is going on. I more or less get the concept of the OnePole filter (and thanks for your help with that), but I’m still a bit unclear on what each bit of code is doing here and how the different bits of code interact. Speaking of which, what does the argument to lag() represent? And what is the range of reasonable values to pass to that method?

Anyway, here’s the full example:

(
SynthDef('wow', {
	|freq = 60, amp = 0.1, gate = 1, wowrelease = 3|
	var chorus, source, filtermod, env, snd;
	chorus = freq.lag(2) * LFNoise2.kr([0.4, 0.5, 0.7, 1, 2, 5, 10]).range(1, 1.02);
	source = LFSaw.ar(chorus) * 0.5;
	filtermod = SinOsc.kr(1/16).range(1, 10);
	env = Env.asr(1, amp, wowrelease).kr(2, gate);
	snd = LPF.ar(in: source, freq: freq * filtermod, mul: env);
	Out.ar(0, Splay.ar(snd))
}).add;
)

a = Array.fill(6, { Synth('wow', [freq: rrand(40, 70).midicps, amp: rrand(0.1, 0.5)]) });

I would say in this context, since you are just creating 6 static Synth Nodes on the server, you wouldnt need to smooth the freq parameter, which is an argument to your SynthDef.

The .lag method creates a Lag Ugen Lag | SuperCollider 3.14.0 Help
You can read more about its argument (time in seconds) in the helpfile.

1 Like

OK, thanks! That definitely clears it up … mostly. Enough that I think I can move forward on my own.

It doesn’t hurt anything, but you’re right, if you create a synth with one frequency and don’t change it, then the lag will have no audible effect.

If you .set a new frequency, then the synth will slide to the new frequency over 2 seconds.

freq is already a (control rate) signal, even without .lag.

hjh

1 Like

Oh. Huh? I don’t understand that. Because it originates from rrand(40, 70) … I would have thought it was just a number. How does it become a signal? Is it because it’s passed as an argument to a SynthDef?

Is there a tutorial or guide that explains this? Because it seems like pretty important, fundamental knowledge.

Hm, except that your SynthDef says |freq = 60...| – the rrand value is not known to the SynthDef at the time of building it.

There’s a brief mention of synth argument handling here but granted, this isn’t likely to be found from beginning tutorials.

First concept: If a value is changing in the server, then it must be a signal. The server can’t change anything about the structure of a SynthDef. A hardcoded number is part of that structure; so if a number needs to be not-hardcoded, then it must be a signal.

Second concept: the Control UGen. Control is unique because it takes input from the language client (instead of taking input from other UGens, like most units do). Function arguments are consolidated first, by category, and then one Control unit is made per category (except literal arrays, which would be folded in with the normal kr args).

The SynthDef here has |freq = 60, amp = 0.1, gate = 1, wowrelease = 3|, so the SynthDef builder finds four arguments, no special cases. So the first thing in the final SynthDef is a Control UGen, with 4 channels, associated with the names freq, amp, gate and wowrelease. (You can .trace a running Synth, and if you do, you’ll find that the first unit is Control, with 4 output values for this SynthDef.) Each of the 4 channels is represented by an OutputProxy; during SynthDef building, freq is assigned the first OutputProxy, amp is assigned the second, and so on. The default values of these output channel proxies are taken from the argument list too.

At Synth(\wow, [freq: rrand(40, 70)...]) time, the OSC message to the server includes a pair of arguments "freq", 53 (for instance). When the synth starts playing, the Control output channel for frequency is set to the value 53 then (or whatever is in the OSC message – 53 could be replaced by any valid rrand result).

freq.lag(2) applies a Lag filter to the control signal coming from the Control UGen (to the freq channel, not the other channels).

James McCartney said once that he thought it might have been a mistake to auto-promote function arguments to synth controls, because the relationship between args and Control channels is, erm, less than obvious. And others have argued that everyone should always write synth controls as e.g. \freq.kr(60) (though I think that case was overstated, and I continue to use function args for normal, single-valued kr controls).

I guess that tutorials don’t go into this at the initial stages because it’s a rather large ball of concepts to throw at a new user. But agreed, it would be better if Getting Started had some links to a more complete description.

hjh

1 Like

Not sure why this thread is so technical. The effect of calling lag on a frequency parameter is to achieve portamento… if you change the arg to .lag(100) the glide will occur more slowly.

one voice:

a = Synth('wow', [freq: 60]);
\\then
a.set(\freq, 120)

or in the context of the array of voices:

a = Array.fill(6, { Synth('wow', [freq: rrand(40, 70).midicps, amp: rrand(0.1, 0.5)]) });
\\then
a.do{|x| x.set(\freq, rrand(40, 70).midicps)}; 
3 Likes

This is exactly what I wanted to write!

1 Like

Ah! Thank you! That definitely makes things clearer.