Improving Documentation - Pfunc and Pfuncn [SOLVED]

Hello everyone!

I would like to beggin the discussion on this category by pointing some documentations that are problematic or not helpful, specially for begginers.

Pfunc examples:

(
var a, b, c;
a = Pfuncn({ exprand(0.1, 2.0) + #[1, 2, 3, 6].choose }, 2);
b = Pfuncn({ #[-2, -3].choose }, 2);
Pseq([a, b], inf).asStream.nextN(20).postln;
)

Sound example

(
SynthDef(\help_sinegrain,
{ arg out = 0, freq = 440, sustain = 0.05;
var env;
env = EnvGen.kr(Env.perc(0.01, sustain, 0.2), doneAction: Done.freeSelf);
Out.ar(out, SinOsc.ar(freq, 0, env))
}).add;
)

(
var a;
a = Pfunc({ exprand(0.1, 0.3) + #[1, 2, 3, 6, 7].choose }).asStream;
{
a.do { |val|
Synth(\help_sinegrain, [\freq, val * 100 + 300]);
0.02.wait;
}
}.fork;
)

IMO, Pfunc doc have many troubles:

  • It creates a function for someting that already exists: Pxrand implements the same thing in a more simpler way;

  • As this sound example is about patterns, itĀ“s more clear for begginers to see stuff implemented used Pbind instead of .fork (a routine?);

  • The numerical example is the same of the sound one, it is a waste of documentation space, although I think every pattern must have numerical examples;

  • It creates a SynthDef that is sonic useless, it does not teach anything related to Pfunc, it would be better if used the default or the best option (IMO) would if SC had diferent default synths for using in diferent generic examples (piano, glide instruments, noise, etc).

  • Docs of Pfunc and Pfuncn are identical, so it does not help anything at allā€¦

Can someone point one a trully begginer usage for Pfunc? I was thing about using it as a way to create a sinusoidal pitch vibrato with \freq on the default synth. BTW I dont know how to make itā€¦ :see_no_evil:

's
ZĆ© Craum

mod note: topic moved to the questions category.

the Learn to Contribute category is intended for learning resources that pertain to software development.

from the help docs ā€œPattern Guide 02: Basic Vocabularyā€:

Pfunc(nextFunc, resetFunc)
The next value is the return value from evaluating nextFunc. If .reset is called on a stream made from this pattern, resetFunc is evaluated. The stream will run indefinitely until nextFunc returns nil.


this is not possible with patterns - you will need to modify the default SynthDef in order to achieve a sinusoidal vibrato. If you look at Event.sc, you can see SynthDef for the default synth:

SynthDef(\default, { arg out=0, freq=440, amp=0.1, pan=0, gate=1;
			var z;
			z = LPF.ar(
				Mix.new(VarSaw.ar(freq + [0, Rand(-0.4,0.0), Rand(0.0,0.4)], 0, 0.3, 0.3)),
				XLine.kr(Rand(4000,5000), Rand(2500,3200), 1)
			) * Linen.kr(gate, 0.01, 0.7, 0.3, 2);
			OffsetOut.ar(out, Pan2.ar(z, pan, amp));
		}, [\ir]).add;

you could modify this to incorporate some vibrato arguments:

SynthDef(\default, { arg out=0, freq=440, amp=0.1, pan=0, gate=1, vibrato_width=0, vibrato_rate=0;
			var z;
			z = freq  * (2 ** (SinOsc.ar (vibrato_rate) * vibrato_width / 12);
			z = LPF.ar(
				Mix.new(VarSaw.ar(z + [0, Rand(-0.4,0.0), Rand(0.0,0.4)], 0, 0.3, 0.3)),
				XLine.kr(Rand(4000,5000), Rand(2500,3200), 1)
			) * Linen.kr(gate, 0.01, 0.7, 0.3, 2);
			OffsetOut.ar(out, Pan2.ar(z, pan, amp));
		}, [\ir]).add;

and then executing:

Synth (\default, [ \vibrato_width, 0.3, \vibrato_rate, 1.5 ])

where vibrato_width is a value of semitones.

alternatively, you could use a time-based pattern to alter the pitch of the sequence of notes according to a sinusoid:

(
Pbind (
	\instrument, \default,
	\delta, 0.2,
	\freq, 440 * (2 ** (sin (Ptime () * 1.5 * 2pi) * 0.3 / 12)),
).play;
)

just bear in mind that in this case the notes themselves are not actually vibrato-ing. each note holds its frequency, while frequencies passed to the synths oscillate up and down from 440 Hz, by 0.3 semitones, at a rate of 1.5cps.

edit: por che no los dos?

(
Pbind (
	\instrument, \default,
	\delta, 0.2,
	\freq, Pseq ([440, 550, 660], inf) * (2 ** (sin (Ptime() * 17.reciprocal * 2pi) * 0.3 / 12)),
	\vibrato_width, sin (Ptime () * 13.reciprocal * 2pi) * 0.3,
	\vibrato_rate, 2.1 + (2 * (sin (Ptime () * 9.reciprocal * 2pi))),
).play;
)

Thanks! Got the vibrato stuff.

Yeah, I do understand what it does, the description is clear, although the example does not help. Passing a exponential function inside it is the same of using Pexprandā€¦ Can you point out a really useful/usual utilization for Pfunc and Pfuncn?

from the help documentation, ā€œPattern Guide Cookbook 03: External Controlā€:

Control of parameters by MIDI or HID

The best approach is to save an incoming value into a variable, and then use Pfunc to access the variable for each event.

(
~legato = 1;
c = MIDIFunc.cc({ |value, num, chan, src|
   ~legato = value.linlin(0, 127, 0.1, 2.5)
}, 1);    // 1 means modwheel
)

(
p = Pbind(
   \degree, Pwhite(-7, 12, inf),
   \dur, Pwrand([0.25, Pn(0.125, 2)], #[0.8, 0.2], inf),
   \legato, Pfunc { ~legato }    // retrieves value set by MIDI control
).play;
)

p.stop;
c.free;

each time the Pbind assembles a note event, Pfunc directs it to whatever is currently inside ~legato, so you can control that parameter in realtime by live coding, using a midi / osc listener, etc.

1 Like

The way to think about Pfunc is this: It allows you to take any function and turn it into a pattern. The reset function is there so you can write patterns that have a state (e.g. if you write a pattern that always starts from 1) - you can make sure it always starts from the place itā€™s supposed to start. Thatā€™s probably a bad example as Prout with a routine would be a better way of doing that, but hopefully you get the idea.

Pfuncn only works for stateless functions, and just calls the function ā€˜nā€™ times before ending.

I often use Pfunc and Prout as a way to create templates for patterns I use a lot.

1 Like

you can use it to live code! for example:

(
SynthDef (\folded_sine) {
	arg lag = 0;
	var sig;
	sig = SinOsc.ar (\freq.kr (330, lag));
	sig = (sig * \fold.kr (0, lag)).fold2 (1);
	sig = Pan2.ar (sig, \pan.kr (0, lag), \amp.kr (0.1, lag));
	Out.ar (0, sig);
}.add;

~pattern = Pmono (\folded_sine,
	\delta, Pfunc { ~delta.choose },
	\freq, Pfunc { ~chord.choose.midicps * ~octaves.choose },
	\fold, Pfunc { ~fold.choose },
	\lag, Pfunc { ~lag },
	\pan, Pwhite (-1.0, 1.0),
);

~delta = [ 2 / 7 ];
~chord = [ 0, 3, 7, 10, 14, 17 ] + 62;
~fold = [ 1 ];
~octaves = [ 1 ];
~lag = 0;
)

and then evaluate the following, line by line:

~play = Ppar (~pattern!6, inf).play
~lag = 0.2
~fold = (1..3)
~delta = (2..4) / 7
~octaves = 2 ** (-1..1)
~octaves = [ 1 ]
~chord = [ 0, 3, 7, 10, 14, 17 ] + 62
~chord = [ 0, 4, 7, 14, 18, 21 ] + 61
~chord = [ 0, 3, 7, 10, 14, 17 ] + 60
~chord = [ 0, 4, 7, 14, 18, 21 ] + 59
~delta = (1..4).nthPrime.reciprocal
~fold = [ 1 ]

~play.stop

by constantly directing the pattern to an environment variable, Pfunc lets you control pattern parameters in real time, ie. without having to rebuild the pattern.

1 Like

I am more used to do this with Pdef, Pbind, Pbindf and Pbindef. Why exactly do you prefer to use Pmono instead of glueing everything with Pbind? Is it to have faster independent control of each key-value pairs (somehow typing less) or is it just a matter of preference?

I got the point of the MIDI example, and there seems to me an usage that is strictly unique to Pfunc.

Just to be 100% sure, any other example where one could only use Pfunc or Pfuncn? (especially something that does not involves MIDI)

Pmono is for when you want to just use the one continuous synthesiser that lives on the server, and the pattern changes its parameters by sending it .set messsages.

Pbind is for when you want to instantiate a separate synth for each note event (if the tail of the previous note overlaps the start of the next note, for example).

edit 2:

oop yup no I see what you mean.

Iā€™m not sure if there is a unique case that couldnā€™t be done in any other way - I think it is more a convenience thing, letting you access environment variables and non-pattern specific functions etc.

Hum, I see. From what Iā€™ve found it seems that people use it as a way to avoid patterns abstraction and use conditional, loops and iteration like some commercial programming languages (I also find that using Pif resembles a really strange visual code ). I guess thatā€™s why David Cottle put this example on his book:

(
SynthDef("SimpleTone",
{arg midinote = 60, amp = 0.9, dur = 1, pan = 0, legato = 0.8;
Out.ar(0,
Pan2.ar(
SinOsc.ar(midinote.midicps, mul: amp)
*
EnvGen.kr(Env.perc(0, dur*legato), doneAction: 2),
pan
)
)
}).add;
)

(
f = 100;
Pbind(
\instrument, "SimpleTone",
\midinote, Pfunc({		
f = ([3/2, 4/3].choose) * f;
if(f > 1000, {f = f/8}); //.fold or .wrap didn't do what I wanted
f.cpsmidi
}),
\dur, 0.2
).play
)

I often see people using both Pfunc and Pkey within Pbind for different pairs (especially when they what to access some keys of the current event), but I donā€™t see any good reason for doing so, despite of convenience or style. Is there any optimization or memory allocation issue?

Is this doing exactly the same or is there any problem/constrain involved with one of the methods?

(
Pbind(
	\degree, Pfunc({|e| (e.dur * 4).postln;}) , //could be any enviroment variable a, b, c, d, e, f....
).play;
)

(
Pbind(
	\degree, (Pkey(\dur)*4 ).trace, 
).play;
)

are convenience or style not reason enough?

I would say probably not, but I am not sure.

\degree, Pfunc({|e| (e.dur * 4).postln;}) , //could be any enviroment variable a, b, c, d, e, f....

although this is not mentioned in the help file, looks like Pfunc passes in new events created by the Pbind into its function as an argument.

I would say using an environment variable here confuses the issue: e is being used within the scope of Pfunc as a container for the event being passed in - you may as well use a more descriptive name like event or note_event or this_event, and leave the environmental variables for uses that require the larger scope.

but yes - the two techniques look pretty equivalent to me.

Sure! I just wonder if this could be clear in the documentation. I will pull a request on github.

Just to clarify: a,b,c,d (when not defined locally) are interpreter variables, not environmental variables (see Interpreter help file). Only in a restricted sense they are global - they cannot be used from class code. But in this context e is a variable local to the function.
A help file describing and comparing different variable types in SC would be valuable IMO (I know, should do, not complain).

Daniel

1 Like

It is mentioned in the help file:

nextFunc Stream function. In an event stream receives the current Event as argument.

Yes, but Pfunc is more versatile. There have been numerous requests in the past, where Pkey was not applicable for various reasons.

ā€¦ but granted, regarding the importance of this feature this is extremely tight, an example would be appropriate.

1 Like

right you are! been reading with my eyes closed again

TIL interpreter variables

Itā€™s worth pointing out that SuperCollider doesnā€™t have any global variables. If you change the environment (which can happen quite easily in ways that are not obvious to inexperienced users) they will no longer hold the same values.

This is a common (and to be honest quite reasonable) misunderstanding.

1 Like

Can you remember one of them here?

E.g. see this thread, granted a quite exceptional case, but there have often been requests concerning such special situations where Pkey is not or hardly applicable, Pfunc is always possible in some way.

https://www.listarc.bham.ac.uk/lists/sc-users-2013/msg28267.html

A FAQ is the the combination of Pif, Pkey and ā€˜==ā€™, which needs Pbinop. This is not Pkeyā€™s fault but it forces an unintuitive syntax. The Pfunc variant is not shorter and also not intuitive per se, but it follows its standard scheme.

Pbind(
    \midinote, Pwhite(60, 62),
    \dur, 0.2,
    \amp, Pif(Pbinop('==', Pkey(\midinote), 61), 0.5, 0.05)
).play


Pbind(
    \midinote, Pwhite(60, 62),
    \dur, 0.2,
    \amp, Pfunc { |e| (e[\midinote] == 61).if { 0.5 }{ 0.05 } }
).play
2 Likes