Here’s a improved version regarding echo - which is a bit more complicated also …
In the previous version the echo delay time was subtracted from the source event time (forcing faded shortening of events). This can be circumvented by doing another routing of the delayed re-routed signal to alternating buses. You can define more buses for allowing more overlaps with longer delay times. This idea will be also used in the VSTPlugin + PbindFx examples that I’ll post later on. However the limitation which remains is the possible overlapping of incoming events, you’d have to either avoid or accept it or read different incoming events from different audio channels (if this is possible at all in your setup).
(
MIDIClient.init;
// insert your MIDI device data here
~midiout = MIDIOut.newByName("MT4", "Port 1");
CmdPeriod.add( { (0..15).do { |i| ~midiout.allNotesOff(i) } });
)
// routing and fx SynthDefs
(
SynthDef(\routeBack_1, { |out = 0, inBus, delay = 0, totalDur = 1, att = 0.01, rel = 0.01|
var src = In.ar(inBus, 2);
var env = EnvGen.ar(Env([0, 0, 1, 1, 0], [delay, att, totalDur - att - rel, rel]), doneAction: 2);
OffsetOut.ar(out, DelayL.ar(src, delay, delay) * env);
}).add;
SynthDef(\routeBack_2, { |out = 0, inBus, totalDur = 1, att = 0.01, rel = 0.01|
var src = In.ar(inBus, 2);
var env = EnvGen.ar(Env([0, 1, 1, 0], [att, totalDur - att - rel, rel]), doneAction: 2);
OffsetOut.ar(out, src * env);
}).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;
)
(
// groups needed for proper order of synths
~routeBackGroup_1 = Group.new;
~routeBackGroup_2 = Group.after(~routeBackGroup_1);
~fxGroup = Group.after(~routeBackGroup_2);
)
// ATTENTION: your setup might require other SoundIn args !
// you might get feedback if SoundIn refers to your mic !
(
// route audio from external device to dedicated bus
~routeBackBus_1 = Bus.audio(s, 2);
~toRouteBackBus_1 = { Out.ar(~routeBackBus_1, SoundIn.ar([0, 1])) }.play(~routeBackGroup_1);
// buses to dispatch re-routed signal
~overlapNum = 3;
~routeBackBuses_2 = { Bus.audio(s, 2) } ! ~overlapNum;
)
// timing has to be done in the midi pattern
(
p = Pbind(
\type, \midi,
\midinote, Pwhite(25, 80) + Prand([0, [0, 7], [0, 12]], inf),
\amp, 1,
// partial application: short for .collect { |x| ~dur = x }
\dur, Prand([0.6, 0.3, 0.3], inf).collect(~dur = _),
\midiout, ~midiout,
\chan, 0,
\legato, 0.05
);
// fxOrder needs to be determined here for delaying
q = Pbind(
\instrument, \routeBack_1,
\dur, Pfunc { ~dur },
\inBus, ~routeBackBus_1,
\count, Pseq((0..~overlapNum-1), inf),
\out, (~routeBackBuses_2[0].index + (Pkey(\count) * 2)).collect(~out = _),
\fxOrder, Prand([0, 2, 1, [2, 1]], inf).collect(~fxOrder = _),
\delay, (Pfunc { |e| e.fxOrder.asArray.includes(2).if { 0 }{ 0.2 } }).collect(~delay = _),
// envelope for re-routed audio for safety
\att, 0.01,
\rel, 0.01,
// lag because of MIDI latency
\lag, 0.04,
\totalDur, Pkey(\delay) + Pkey(\dur),
\group, ~routeBackGroup_2
);
r = PbindFx([
\instrument, \routeBack_2,
// read frfom alternating buses
\inBus, Pfunc { ~out },
// exceptional bus reading
\otherBusArgs, [\inBus],
// take over data from midi master
\dur, Pfunc { ~dur },
\totalDur, Pkey(\dur) + Pfunc { ~delay },
// envelope for re-routed audio for safety
\att, 0.01,
\rel, 0.01,
// lag because of MIDI latency
\lag, 0.04,
\group, ~fxGroup,
\fxOrder, Pfunc { ~fxOrder },
\cleanupDelay, Pkey(\totalDur)
],[
// 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, \echo,
\echoDelta, 0.075,
\maxEchoDelta, 0.2,
\decayTime, Pwhite(1.0, 2),
\cleanupDelay, Pkey(\decayTime)
]
);
// delay for data sharing
x = Ptpar([0.0, p, 0.0001, q, 0.0002, r]).play
)
// stop and cleanup
x.stop
~toRouteBackBus_1.free