VSTPlugin + PbindFx

Hi,

checked some VSTPlugin + PbindFx combos, which can be fun!

I’m following some VSTPlugin examples with sequencing – they still work, but meanwhile Christof has added the improved \vst_midi and \vst_set event types, which I’m using in the new examples.

Different situations with VSTis and VST fxs might occur: we can e.g. take a VSTi as source (Ex.1), a generic SC source and a VST fx (Ex. 2a) or a VST fx plus generic SC fxs (Ex. 2b). More complicated combinations of generic SC and VST sources and fxs can be treated with the same principles.

As PbindFx uses a dedicated Event type, the common trick is to re-route the signal to another synth.

The following examples require miSCellaneous_lib v0.22, VSTPlugin v0.3.2 and two VST units:
Download and install ComboV organ and FuzzPlus3 from

// Helper SynthDefs and Fxs:

(
// vsti SynthDef
// set reverb (param 33) to 0

SynthDef(\organ, { |out = 0|
	var sig = VSTPlugin.ar(nil, 1, params: [33, 0]);
	OffsetOut.ar(out, sig);
}).add;


// copy SynthDef, only reads from bus

SynthDef(\copy, { |out = 0, srcIn, gate = 1, dur = 1, legato = 1, att = 0.01, rel = 0.05|
	var in = In.ar(srcIn, 1);
	var env = EnvGen.ar(Env.asr(att, 1, rel), gate, doneAction: 2);
	OffsetOut.ar(out, in * env ! 2);
}).add;


// Fxs
// All ins and outs use two channels

// spat fx
// This effect introduces a very small delay,
// in examples balancing by lag (as it obviously has to be done with echo) is neglected.

SynthDef(\spat, { |out, in, freq = 1, maxDelayTime = 0.005,
    amp = 1, mix = 1|
    var sig, inSig = In.ar(in, 2);
    sig = DelayC.ar(
        inSig,
        maxDelayTime,
        { LFDNoise3.ar(freq, maxDelayTime, maxDelayTime/2) } ! 2,
        amp
    );
    Out.ar(out, (1 - mix) * inSig + (sig * mix));
}).add;


// echo fx, always unified delay maxEchoDelta

SynthDef(\echo, { |out, in, maxEchoDelta = 0.2, echoDelta = 0.1,
    decayTime = 1, amp = 1, mix = 1|
    var sig, inSig = In.ar(in, 2);
    sig = DelayL.ar(
        CombL.ar(inSig, maxEchoDelta, echoDelta, decayTime, amp),
        maxEchoDelta,
        maxEchoDelta - echoDelta
    );
    Out.ar(out, (1 - mix) * inSig + (sig * mix));
}).add;


// wah-wah fx

SynthDef(\wah, { |out, in, resLo = 200, resHi = 5000,
    cutOffMoveFreq = 0.5, rq = 0.1, amp = 1, mix = 1|
    var sig, inSig = In.ar(in, 2);
    sig = RLPF.ar(
        inSig,
        LinExp.kr(LFDNoise3.kr(cutOffMoveFreq), -1, 1, resLo, resHi),
        rq,
        amp
    ).softclip;
    Out.ar(out, (1 - mix) * inSig + (sig * mix));
}).add;


// reverb fx
// rough estimation: freeVerb's room arg = decayTime / 10

SynthDef(\reverb, { |out, in, damp = 0.5,
    decayTime = 10, amp = 1, mix = 1|
    var sig, inSig = In.ar(in, 2);
    Out.ar(out, FreeVerb.ar(inSig, mix, min(decayTime, 10) / 10, damp, amp));
}).add;
)




// 1) Fx sequencing with a VSTi
// For overlapping Events we need more VSTi units, they are played in sequence. Groups for order of evaluation.


// wait a few seconds after evaluating, CPU-spikes !
(
~vstGroup = Group.new;
~copyGroup = Group.new(~vstGroup, \addAfter);

~bus = Bus.audio(s, 10);
~synths = 0!10;
~ctrls = { |i|
	~synths[i] = Synth(\organ, [\out, ~bus.subBus(i)], ~vstGroup);
	VSTPluginController.new(~synths[i]).open("ComboV")
} ! 10;
)


(
// play vstis in sequence
// need data sharing with variables
p = Pbind(
	\type, \vst_midi,
	\count, Pseq((0..9), 10).collect(~count = _),
	\vst, Pkey(\count).collect(~ctrls[_]),
	\dur, Prand([0.1, 0.1, 0.2] * 1, inf).collect(~dur = _),
	\legato, Pwhite(0.1, 1.5).collect(~legato = _),
	\midinote, Pxrand((48..70), inf) + [0, 5],
);


q = PbindFx([
		\instrument, \copy,
		\dur, Pfunc { ~dur },
		\legato, Pfunc { ~legato },
		\att, 0.01,
		\rel, 0.05,
		// only read from buses
		\srcIn, ~bus.index + Pfunc { ~count },
		\group, ~copyGroup,
		\otherBusArgs, [\srcIn],
		\amp, 0.2,

		\fxOrder, Prand([1, 2, [1, 2], [2, 1]], inf),
		// With a sustained envelope \cleanupDelay refers to the maximum release time,
		\cleanupDelay, Pkey(\rel) + 0.1
	],[
		\fx, \spat,
		// oscillation of delay -> frequency modulation of source signal
		\freq, Prand([1, 2, 3], inf),
		\maxDelayTime, 0.005,
		\cleanupDelay, Pkey(\maxDelayTime)
	],[
		// variation by sequencing of params
		\fx, \wah,
		\mix, Pseq([0.2, 0.5, 0.7], inf),
		\cutOffMoveFreq, Pseq([1, 2, 5, 10], inf),
		\cleanupDelay, 0.01
    ]
);

x = Ptpar([0.0, p, 1e-5, q]).play
)

// cleanup

x.stop;
~synths.do(_.free);


//////////////////////////

// wait a few seconds after evaluating, CPU-spikes !

(
~vstGroup = Group.new;
~copyGroup = Group.new(~vstGroup, \addAfter);

~bus = Bus.audio(s, 2);
~synths = 0!2;
~ctrls = { |i|
	~synths[i] = Synth(\organ, [\out, ~bus.subBus(i)], ~vstGroup);
	VSTPluginController.new(~synths[i]).open("ComboV")
} ! 2;
)


// this example employs echo, which requires lagging non-echo events (see Ex.2b-d from PbindFx help)
(
p = Pbind(
    \type, \vst_midi,
	\count, Pseq((0..1), inf).collect(~count = _),
	\vst, Pkey(\count).collect(~ctrls[_]),
	\dur, Pn(Pshuf(0.2!6 ++ Rest(0.2))).collect(~dur = _),

	\legato, (Pfunc { |e|
		rrand(0.3, 0.8) / (e.fxOrder.asArray.includes(2).if { 10 }{ 1 })
	}).collect(~legato = _),

	\fxOrder, Pn(Pshuf([[1,2], [1,2,4], 1, 2, 3, [1,3], [2,3]])).collect(~fxOrder = _),

	// lag must be adapted to maxEchoDelta
	\lag, (Pfunc { |e| e.fxOrder.asArray.includes(2).if { 0 }{ 0.2 } }).collect(~lag = _),

	// downwards tendency + chord sequence
	\midinote, Pseq((90, 80..50), inf) +
		Pn(Pshuf([0, 0, 0, -2, 3, 5, 7, [0, 7], [0, 7]])),
);


q = PbindFx([
		\instrument, \copy,
		\dur, Pfunc { ~dur },
		\legato, Pfunc { ~legato },
		\att, 0.01,
		\rel, 0.05,
		\srcIn, ~bus.index + Pfunc { ~count },
		\group, ~copyGroup,
		\otherBusArgs, [\srcIn],
		\amp, 0.2,

		\fxOrder, Pfunc { ~fxOrder },
		\lag, Pfunc { ~lag },

        // With a sustained envelope \cleanupDelay refers to the maximum release time,
		\cleanupDelay, Pkey(\rel) + 0.1
	],[
		\fx, \spat,
		\freq, Prand([1, 2, 3], inf),
		\maxDelayTime, 0.005,
		\cleanupDelay, Pkey(\maxDelayTime)
	],[
		\fx, \echo,
		\echoDelta, Pseq((1..10)/50, inf),
		\decayTime, 1,
		\cleanupDelay, Pkey(\decayTime)
	],[
		// variation by sequencing of params
		\fx, \wah,
		\mix, Pseq([0.2, 0.5, 0.7], inf),
		\cutOffMoveFreq, Pseq([1, 2, 5, 10], inf),
		\cleanupDelay, 0.01
	],[
		\fx, \reverb,
		\mix, 0.3,
		\damp, 0.1,
		\decayTime, Pwhite(1.0, 5),
		\cleanupDelay, Pkey(\decayTime)
	]
);

x = Ptpar([0.0, p, 1e-5, q]).play
)


// cleanup

x.stop;
~synths.do(_.free);

//////////////////////////////

// 2) Fx sequencing with a VST Fx

// 2a) Applying only a VST Fx

// This is probably easiest without PbindFx

(
SynthDef(\fuzzPlus3, { arg out, in;
    Out.ar(out, VSTPlugin.ar(In.ar(in, 2), 2));
}).add;

SynthDef(\sawPerc_2ch, { |out, freq = 400, att = 0.005, rel = 0.05,
	amp = 0.5, pan = 0|
	var sig = Pan2.ar(Saw.ar(freq, amp), pan);
	OffsetOut.ar(
		out,
		sig * EnvGen.ar(Env.perc(att, rel), doneAction: 2)
	)
}).add;

SynthDef(\copyFixedDur, { |out = 0, srcIn, fixedDur = 1, att = 0.01, rel = 0.01|
	var in = In.ar(srcIn, 1);
	var env = EnvGen.ar(Env.linen(att, fixedDur - att - rel, rel), doneAction: 2);
	OffsetOut.ar(out, in * env ! 2);
}).add;
)


// wait a few seconds after evaluating, CPU-spikes !

(
// estimate fx num from maximum overlap
~fxNum = 5;
~fxBus = Bus.audio(s, ~fxNum * 2);
~synths = 0 ! ~fxNum;
~ctrls = { |i|
	~synths[i] = Synth(\fuzzPlus3, [in: i * 2 + (~fxBus.index)]);
	VSTPluginController(~synths[i]).open("FuzzPlus3")
} ! ~fxNum;
)


// choose different params by setting the used fx Synths
(
p = Pbind(
	\type, \vst_set,
	\count, Pseq((0..~fxNum-1), inf).collect(~count = _),
	\vst, Pkey(\count).collect(~ctrls[_]),

	\dur, Pn(0.1).collect(~dur = _),
	0, Pwhite(0.5, 1),
 	2, Prand([0.25, 1], inf),
 	\params, [0, 2], // look up parameter 0 and 2
);

// src events can overlap, so play to different fx instances
q = Pbind(
	\instrument, \sawPerc_2ch,
	\amp, 0.02,
	\out, ~fxBus.index + Pfunc { ~count * 2 },
	\pan, Pstutter(Pexprand(1, 2), Pwhite(-1.0, 1)),
	\dur, Pfunc { ~dur },
	\att, Pwhite(0.005, 0.01),
	\rel, Pwhite(0.03, 0.3),
	\midinote, Pseq([
		Pstutter(Pexprand(1, 3), Pwhite(35.0, 85)) + Prand([0, [0, 7], [0, 7, 14]], 150),
		Pstutter(30, Pwhite(80.0, 90, 1) + [0, 7, 14, 21])
	])
);

x = Ptpar([0.0, p, 0.00001, q]).play
)



// cleanup

x.stop;
~synths.do(_.free);


// 2b) Applying VST plus generic SC fxs

// We then again need re-routing, here generic SC fxs are applied after the VST fx


// wait a few seconds after evaluating, CPU-spikes !

(
~fuzzNum = 10;
~fuzzBus = Bus.audio(s, ~fuzzNum * 2);
~copyBus = Bus.audio(s, ~fuzzNum * 2);

~vstGroup = Group.new;
~copyGroup = Group.new(~vstGroup, \addAfter);

~synths = 0 ! ~fuzzNum;
~ctrls = { |i|
	~synths[i] = Synth(\fuzzPlus3, [in: i*2 + ~fuzzBus.index, out: i*2 + ~copyBus.index], ~vstGroup);
	VSTPluginController(~synths[i]).open("FuzzPlus3")
} ! ~fuzzNum;
)



// choose different params by setting the used vst fx Synths
// as lagging has to be done because of echo, vst_set is the master also for fxOrder

(
p = Pbind(
	\type, \vst_set,
	\count, Pseq((0..~fuzzNum-1), inf).collect(~count = _),
	\vst, Pkey(\count).collect(~ctrls[_]),

	\dur, Prand([0.3, 0.3, 0.3, 0.6] / 1.5, inf).collect(~dur = _),
	0, Pwhite(0.8, 1),
 	2, Pwhite(0.3, 0.6),

	\fxOrder, Prand([1, 2, [1, 2], [2, 1]], inf).collect(~fxOrder = _),

	\att, Pwhite(0.005, 0.01).collect(~att = _),

	\rel, (Pfunc { |e|
		rrand(0.2, 0.6) / (e.fxOrder.asArray.includes(1).if { 10 }{ 1 }) }
	).collect(~rel = _),

	// lag must be adapted to maxEchoDelta
	\lag, (Pfunc { |e| e.fxOrder.asArray.includes(1).if { 0 }{ 0.2 } }).collect(~lag = _),

	\params, [0, 2], // look up parameter 0 and 2
).trace;

q = Pbind(
	\instrument, \sawPerc_2ch,
	\amp, 0.02,
	\out, ~fuzzBus.index + Pfunc { ~count * 2 },
	\pan, Pstutter(Pexprand(1, 2), Pwhite(-0.8, 0.8)),
	\dur, Pfunc { ~dur },
	\att, Pfunc { ~att },
	\rel, Pfunc { ~rel },
	\lag, Pfunc { ~lag },
	\midinote, Pstutter(Pexprand(1, 3), Pwhite(40.0, 85)) + Pn(Pshuf([0, [0, 7], [0, 5, 9], [0, 12.5]]))
).trace;

r = PbindFx([
		\instrument, \copyFixedDur,
		\dur, Pfunc { ~dur },
		\fixedDur, Pfunc { ~att + ~rel },
		\att, 0.01,
		\rel, 0.01,
		\srcIn, ~copyBus.index + Pfunc { ~count * 2 },
		\group, ~copyGroup,
		\otherBusArgs, [\srcIn],

		\fxOrder, Pfunc { ~fxOrder },
		\lag, Pfunc { ~lag },

 		\cleanupDelay, Pkey(\fixedDur) + 0.5
	],[
		\fx, \echo,
		\echoDelta, Pseq((1..3), inf) / Pfunc { rrand(20.0, 60)  },
		\decayTime, 1,
		\cleanupDelay, Pkey(\decayTime)
	],[
		// variation by sequencing of params
		\fx, \wah,
		\mix, 0.8,
		\cutOffMoveFreq, Pseq([1, 2, 5, 10], inf),
		\cleanupDelay, 0.01
	]
);

x = Ptpar([0.0, p, 1e-5, q, 2e-5, r]).play
)



// cleanup

x.stop;
~synths.do(_.free);

For extremly varying durations a refined managment of bus selection (checking the free ones) could be used, I’ve omitted this option here.

4 Likes