Question about *LPF and ++

Hi,

I would like to understand what these two things mean in this context;

{FreeVerb2.ar (* LPF.ar (PinkNoise.ar (1! 2) + 0.2 * Dust.ar (0.1), 60) ++ [1, 1, 0.2, 1e4]). tanh}

  1. “*” before the LPF
  2. ++ [1, 1, 0.2, 1e4]

from what I understand, * makes sure that the signal is passed to in0 and in1 of FreeVerb2; the total signal it first concatenated (++) with constant signals [1, 1, 0.2, 1e4]?

may you help me?

thanks

Indeed this short writing using syntactic sugar (coming from a SC tweet?) needs a closer look.

.) A ‘*’ before an array inside a functional writing of args splits the array items and passes them to the args. It becomes more clear if you separate it, e.g. by doing this with the method Array.with which genererates a new array and expects separated items. Here you see that it does something slightly different from what you describe, the inner array is first built and then split:

Array.with(*LPF.ar (PinkNoise.ar (1! 2) + 0.2 * Dust.ar (0.1), 60) ++ [1, 1, 0.2, 1e4])
-> [ a LPF, a LPF, 1, 1, 0.2, 10000 ]

This is equivalent to

Array.with(*(LPF.ar (PinkNoise.ar (1! 2) + 0.2 * Dust.ar (0.1), 60) ++ [1, 1, 0.2, 1e4]))
-> [ a LPF, a LPF, 1, 1, 0.2, 10000 ]

Also interesting what’s going on inside the LPF: PinkNoise.ar(1!2) is very short writing for getting decorrelated stereo pink noise. Instead you could equivalently write

[PinkNoise.ar(1), PinkNoise.ar(1)]

or

[PinkNoise.ar, PinkNoise.ar]

or

{ PinkNoise.ar } ! 2

or

{ PinkNoise.ar }.dup

but not

PinkNoise.ar ! 2

which generates a correlated pink noise.

Concerning

PinkNoise.ar (1!2) + 0.2 * Dust.ar(0.1)

SC’s left-right precedence causes an offset of 0.2 to be added (to stereo noise) before the multiplication with dust impulse. The sigmoid function tanh keeps the signal between -1 and + 1.
Concerning the offset of 0.2. I have only an assumption: the help file of PinkNoise says:

NOTE: The values produced by this UGen were observed to lie with very high probability between approximately -0.65 and +0.81 (before being multiplied by mul).

So it might be that the intention was to keep the signal roughly below 1 with very high probability also.

2 Likes

@dkmayer answered this very thoroughly, so just one addition:
Multichannel / array expansion in SuperCollider is one of the most powerful features of the language, but it’s easy to lose track of what it’s doing no matter your experience level. It helps in the case you described to break things out into separate pieces and use a straightforward postln to see if you’re getting what you expect. You can add a .postln without affecting the value of it’s object - it’s effectively transparent.

sig = PinkNoise.ar(1 ! 2).postln;
sig = LPF.ar (sig + 0.2 * Dust.ar(0.1), 60).postln;
sig = (sig ++ [1, 1, 0.2, 1e4]).postln;
sig = FreeVerb2.ar(*sig).postln;

… you get the picture. I still do this regularly with my own synths to track down issues.


Also, I make very heavy use of this little baby class extension: https://gist.github.com/scztt/830bb7ff5e6aa858540237b95227ae48

This will allow you to assert the channel count you expect in your SynthDef, and will error out if it’s ever not the case. Often, I have things correct initially but when I make some changes later I accidentally change the channelization (e.g. from mono to stereo) - this is often hard or impossible to hear. I’ve been involved in whole performances where we were just pumping out an extra 4 channels that weren’t getting heard because of a weird multichannel expansion case… I’ll usually use this in particularly complex sections of a synth, to ensure I’m getting what I think I’m getting.

// yup, my signal is mono
sig = Dust.ar(1).assertChannels(1);

// yup, my signal is stereo
sig = FreeVerb2.ar(Dust.ar(1)).assertChannels(2); 

// oops, feeding two channels into FreeVerb2 leaves you with 4 channels - this throws an error
sig = FreeVerb2.ar(Dust.ar([1, 1])).assertChannels(2); 
3 Likes

thanks to all; I understood everything;

yes is from a sc-tweet :

// Nathaniel Virgo
(
play{
	p = PinkNoise.ar(1!2);
	BRF.ar(
		p + Blip.ar(p+2,400),
		150,
		2,
		0.1
	)
	+
	LPF.ar( FreeVerb2.ar( *LPF.ar( p + 0.2 * Dust.ar( 0.1 ), 60) ++ [ 1, 1, 0.2, 1e4 ]).tanh, 2000)
}
)
2 Likes

Great example by a master of SC tweets !