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.