Create a routine of Pdefs (or Tdefs?)

Hello and apologies if the following seems a bit chaotic.

Here is the context, followed by the questions:

I have several Synths, which I am playing with Pdefs.
My goal is to create a looping sequence with these Pdefs.
Inside that routine, I would like to play and stop these synths multiple times, sometimes varying one or more parameters.

And here begin the queries:

First of all, I’m trying to figure out if what I need is a Routine, or a Tdef (or both?).
Also, are Pdefs what I should be using, or would Ndefs be more fitting?

Moreover, I’m trying to figure out a syntax where I do not need to write the whole synth each time I want to change one parameter, but instead only call that one parameter that I want to alter.
Something like

z = SynthDef(\name)
z.set(\trig, 1)

And finally, I would like to be able to set a fadeTime in order for the Synths not to start or stop abruptly - and it would be great if I could do that without changing the parameters of the synth (like for example the attack or release of the envelope). Something like the global variable syntax
~synth1.fadeTime = 5;

I’ve tried several combinations of these, some partially working, none successful.

Here are a couple of these synths and a (non-functioning) template of what it could look like:

/* First synth: */

(
SynthDef(\bazz, {
	arg freq=60, cutFreq=600, rq=0.4, amp=0.7, atk=0.01, rel=0.1, mod=1, pan=0, out=0;
	var sig, env;
	env = EnvGen.ar(Env([0,1,0], [atk, rel]), doneAction:2);
	sig = Pan2.ar([Saw.ar(freq, 0.4), Saw.ar(freq+mod, 0.4)], pan) * env;
	sig = RLPF.ar(sig, cutFreq, rq);
	Out.ar(out, sig * amp);
}).add;
)

(
~pbazz = Pdef(\pbazz,
	Pbind(
		\instrument, \bazz,
		\freq, Pwhite(49.0, 51.0),
		\cutFreq, 100,
		\rq, Pwhite(0.03, 0.7),
		\atk, Pwhite(2.5, 6),
		\rel, Pwhite(5.0, 10.0),
		\dur, Pkey(\rel),
		\mod, Prand([1, 2], inf),
		\pan, Pwhite(-0.9, 0.9,inf),
		\amp, 0.1,
		\out,0,
));
)

/* 2nd: */
(
SynthDef(\grain, {
	arg buf=0, rate=1, start=0, amp=0.5, dur=0.3, rel=0.1,  pan=0, gate=1;
    var sig, env, pos;

    env = EnvGen.kr(
        Env.asr(0.01, amp, rel),  
        gate: gate,
        doneAction: 2 
    );
    pos = start * BufSamples.ir(buf);
    sig = PlayBuf.ar(1, buf, rate * BufRateScale.ir(buf), 1, pos, 0);
    sig = sig * env;
    OffsetOut.ar(0, Pan2.ar(sig, pan));
}).add;
)

(
Pdef(\pgrain,
    Pbind(
        \instrument, \grain,
        \buf, ~klar1.bufnum,
        \rate, 1,
        \dur, 0.06,
        \start, Pwhite(0.0, 0.1),  // Random starting point in the buffer
        \amp, 0.5,
		\rel, 0.1,
        \pan, Pwhite(-1.0, 1.0),  // Random panning
        //\gate, 1,  // Gate is open during playback
		\amp, 0.5,
        \out, 0
    )
);
)

/* And a routine template: */

((
Routine({
        3.wait;
        Pdef(\pbazz).play;
	5.wait;
	Pdef(\pbazz).set(\freq, 100);    // parameter variation?
        10.wait;
	 Pdef(\pbazz).stop;
	 5.wait;
	 Pdef(\pgrain).play;
         5.wait;
	 Pdef(\pgrain).set(\rel, 1);
	 5.wait;
	 Pdef(\pgrain).stop;   //fade out?//
	 3.wait;
}).loop.play;
)

Those are mostly the same. The Task helpfile should say something about the difference between a Routine and a Task. IMO Task/Tdef may be slightly preferable for sequencing.

I think you want Pbindef rather than Pdef, then.

hjh

1 Like

Personally, I prefer to use patterns for this…

a = Pdef(...);
b = Pdef(...);

PSeq([a, b]).play

It’s very powerful when you create functions that return patterns. You can also use Ppar to play many at once.

1 Like

I have actually managed to get the Tdef working, so I’m starting to build up the sequence.
Pbindef seems indeed to be more suitable for what i’m trying to achieve, so thank you again for the feedback.
One thing I haven’t managed to get around though is this:
One of my synths is playing some audio files, and from the moment it starts playing the file (within the Tdef), it seems it does not respond to parameter changes (ex: amplitude) or even the stop; message. it will just play out the whole file until the end.
I’m assuming the reason for it is that i’ve baked the buf duration into the envelope, but I did expect that I would be able to change the \amp.
Do you perhaps have another suggestion regarding this?

//synth//
(
SynthDef(\try3, {
	arg out=0, buf=0, rate=1, speed=0.001, pan=0, width=2, amp=0.5;
	var sig, env, panPos;
	env = EnvGen.kr(Env([0, 1, 1, 0], [0.01, BufDur.ir(buf) - 0.02, 0.01]), doneAction:2);
	sig = PlayBuf.ar(2, buf, rate: BufRateScale.kr(buf) * rate, trigger: 0, loop:0, doneAction:2);
	panPos = LFNoise1.kr(speed).range(-1, 1);
	sig = PanAz.ar(4, sig, panPos + (0.25 * width * [-1, 1]));
	sig = sig.sum;
	Out.ar(out, sig * amp);
}).add;
);

// pbindef //
(
Pbindef(\ptry3,
	\instrument, \try3,
	\buf, ~sho1,
	\dur, Pfunc({|d| (d.buf).duration}),
	\width, 2,
	\amp, 0.7,
	\out, 0,
);
);

// Tdef //

(
Tdef(\loopa, {
	loop {
		3.wait;
		Pbindef(\ptry3).play;   
		10.wait;
		Pbindef(\ptry3, \amp, 0);  
		5.wait;
		Pbindef(\ptry3, \amp, 0.7); 
		10.wait;
		Pbindef(\ptry3).stop; 

		Tdef(\loopa).reset.play;
	}}).play;
)

To handle this problem, you need to understand event types.

Pbind / Pbindef etc. don’t play anything. They just provide data. It’s up to the event prototype (usually Event.default) to interpret the data and take action. The default event prototype defines a set of functions, called event types, to do different things.

If you don’t specify an event type, \note will be used. This event type plays a new synth, and schedules its release. Notes may overlap, so a \note-type Pbind sequence can be polyphonic.

Also be aware that nothing tracks these notes. They are launched, and run under their own power, but there’s no ongoing record in the language of which notes are playing what.

To respond to parameter changes, those parameter changes have to be directed to the active note(s) – not the pattern. Pbindef(\ptry3, \amp, 0); means \amp will be 0 for the next event in the sequence.

A simple way to do that is to put synths in a group. Then you can address parameter changes to the group, and it will affect all synths in the group. (Also, if you want notes to release early, use a gated envelope instead of hardcoding time.)

(
Tdef(\loopa, {
	var group = Group.new;
	loop {
		3.wait;
		Pbindef(\ptry3, \group, group).play;
		10.wait;
		group.set(\amp, 0);
		5.wait;
		group.set(\amp, 0.7);
		10.wait;
		Pbindef(\ptry3).stop;
		group.freeAll;
		// you've already got a 'loop {}'...
		// this looks redundant
		// Tdef(\loopa).reset.play;
	}
}).play;  // close braces on their same level
)

(This doesn’t release groups after stopping the Tdef… but I’m out of time for now.)

The ddwVoicer quark provides a node-tracking object, which would also work for this, but I can’t write up an example right now.

hjh

1 Like

Will try to digest this.
Thank you for you time!

This is how I’d do the whole thing without routines.

~mkPBazz = { |freq |
	Pbind(
		\instrument, \bazz,
		\freq, freq,
		\cutFreq, 100,
		\rq, Pwhite(0.03, 0.7),
		\atk, Pwhite(2.5, 6),
		\rel, Pwhite(5.0, 10.0),
		\dur, Pkey(\rel),
		\mod, Prand([1, 2], inf),
		\pan, Pwhite(-0.9, 0.9,inf),
		\amp, 0.1,
		\out,0,
	);
};

~mkRest = { |dur|
	Pbind(\dur, Pseq([dur], 1))
};

Pseq([
	Pfindur(5, ~mkPBazz.(freq: Pwhite(49.0, 51.0))),
	Pfindur(10, ~mkPBazz.(freq: 100)),
	~mkRest.(5),
], inf).trace.play

Now it is slightly different. Rather than mutating already running patterns, you create new patterns.

This means where ~mkPBass is called the second time, it will reset the state of the pattern.

For this reason, it is better to make patterns actually have a real duration (number of repeats before they return) than using Pfindur.

If you then want to combine patterns with different duration, you can make separate patterns for each group of parameters, and use PChain. When this is combined with functions that create patterns, this is very powerful.

If you want to change a parameter live yourself, you can use Pdefn. This does require that you define where you want to do this before running the code. I haven’t tried this with Pbinddef, but it should work?

I really like this approach because you can now take that final Pseq and use that elsewhere, perhaps run a few in parallel, in a sequence, or only for 10 seconds… this is all much hard if you use routines or Tdef.

1 Like

thank you for this alternative!
Will try it out!