ProxyChain playing multiple synth and filter functions flexibly in one proxy
ProxyChain keeps a global repertoire of sound functions by name.
A ProxyChain has an ordered collection of sound functions and uses a nodeproxy
to add or remove the sound functions to/from the signal chain individually, by name.
Like Ndef, Pdef, Tdef, ProxyChain keeps all named instances in a class variable "all".
ProxyChain(<name>) accesses a ProxyChain by name,
ProxyChain(<name>, slotNames, ... ) puts a new instance there.
See also Ndef, NodeProxy, - especially filtering, ProxyChainGui
*add(name, func, name, func, ... ) add globally available functions to ProxyChain.allSources.
(
q = q ? ();
q.numChans = 5;
// add a sound source
ProxyChain.add(
\dust, \mix -> { |dens=20, dustdec=0.02, dustfreq= 600|
Ringz.ar(Dust.ar(dens).lag(0.0001), dustfreq, dustdec)
}
);
// an association with \filter becomes a filter,
// and creates a wet/dry balance on the output.
// several funcs can be added as key1, func1, key2, type -> func2, etc.
ProxyChain.add(
\ringmod, \filter -> { |in, randrate=5|
in.asArray[0] // force mono inputs
* SinOsc.ar(LFNoise0.kr([randrate, randrate]).exprange(300, 3000)).sum
},
\dist, \filter -> { |in, drive=10, amp=0.2| (in.asArray[0] * drive).clip2(0.5) * amp }
);
// an association with \filterIn also becomes a filter,
// but creates the wet/dry balance control on the filter input,
// on on the output like \filter. this can be useful for delays, reverbs etc.
ProxyChain.add(
\riseComb5, \filterIn -> { arg in, delay = 0.023, dlDrift = 0.02, spread=0.5,
decayRise=0.5, decayFall=100;
var delayscales = 2 ** ((0 .. q.numChans - 1) * 2 / (q.numChans - 1) - 1 * spread);
var dels = delayscales.scramble.collect { |dscale|
var timedrift = LFDNoise3.kr(0.3, dlDrift, 1) * dscale;
var ampcomp = (20 * decayRise).dbamp * (decayFall ** -0.25);
var combs;
in = in.asArray[0] * ampcomp.lag(0.2);
combs = (decayFall * [ 1, decayRise]).collect { |decay|
CombL.ar(in, 1, delay * dscale, decay * delay)
};
combs[0] - combs[1]; // combs come in slowly, like formlet.
};
Splay.ar(dels)
},
\ampFin, \filter -> { |in, drive=1, ampLimit=1, lAmp=1|
Limiter.ar(in * drive, ampLimit) * lAmp;
}
);
// add specs for the controls used (for NodeProxyEditor).
Spec.add(\dens, [0.1, 1000, \exp]);
Spec.add(\dustamp, [0, 1, \amp]);
Spec.add(\dustdec, [0.0001, 0.1, \exp]);
Spec.add(\dustfreq, \freq);
Spec.add(\dt, [0.001, 0.2, \exp]);
Spec.add(\dc, [0.01, 100, \exp]);
Spec.add(\drive, [1, 100, \exp]);
Spec.add(\spread, [0, 1, \amp]);
Spec.add(\decayRise, [0, 0.9, \amp]);
Spec.add(\decayFall, [1, 1000, \exp]);
Spec.add(\dlDrift, [0, 0.1, \amp]);
s.boot;
)
*new(key, slotNames, numChannels, server)
create a ProxyChain with its own independent Nodeproxy.
numChannels - of the proxy that is created automatically.
slotNames - define which functions from ProxyChain.allSources will be available in what order.
// the functions can be sources (func, \mix -> func)
// or filters (\filter -> func, \filterIn -> func)
(
c = ProxyChain(\alpha, [\dust, \ringmod, \dist, \riseComb5, \test]);
c.play; // play the proxy
g = c.gui(12); // make a gui for it with 12 slots - see ProxyChainGui
)
// these methods are passed through to the proxy:
c.play;
c.stop;
c.playN;
c.end(2);
*from(proxy, slotNames) make a proxychain from an existing NodeProxy or Ndef.
Ndef(\bla).ar(2);
ProxyChain.from(Ndef(\bla), [\dust, \ringmod, \dist, \riseComb5, \test]);
ProxyChain(\bla).play;
ProxyChain(\bla).add(\dust);
ProxyChain.all;
add(key, wet) add a sound or filter function, wet is dry/wet balance
c.add(\dust, 0.123);
c.add(\dust, 0.2);
c.add(\ringmod, 0.5);
c.add(\dist, 1);
// the automatically generated mix/wet balance names are
// "mix" for sources and "wet" for filters, with an added
// index of 10, 20, 30, etc.
ProxyChain(\xyz, [
\dust, // the first node is a source, so "mix", and here: "mix10",
\ringmod, // filters become "wet", so here, "wet20"
\dist // and "wet30";
]);
c.proxy.fadeTime = 2;
c.add(\riseComb5, 0.2); // \filterIn not show correctly in NodeProxyEditor yet.
// add a local version of a source -
// this overrides the global version of \dust.
(
c.add(\dust, nil, \mix -> { |dens=20, dustdec=0.02, dustfreq= 600|
Ringz.ar(Dust.ar(dens).lag(0.0001), dustfreq * [0.62, 1, 1.62], dustdec).mean
});
)
c.sources.postcs;
c.sources.put(\dust, nil); // back to global dust
c.add(\dust);
c.sources.postcs;
remove(key) remove a sound or filter function.
c.remove(\dist); // nodemap removes settings as well, so ...
c.remove(\ringmod);
c.remove(\riseComb5); // sometimes misses current value - why?
c.slotNames; // all slotnames that are available, in the order they are in;
c.slotsInUse; // which ones are playing now?
c.remove(\dust);
gui(name, buttonList, nSliders)
make a ProxyChainGui window for controlling the slots, and possible additional functions.
// by default, buttonList nil is replaced with control buttons for all slots.
c.gui(20);
// if specified, can be friendlier
(
g = c.gui(20,
[
[ \generators, \label ], // a label only
[ \dust, \slotCtl, 0.25 ], // a control for a slot, starting level
[ '1 > 1', \label ],
[ \ringmod, \slotCtl ], // 0 - dry by default
[ \dist, \slotCtl, 1 ], // 1 - all wet
[ '1 > 5', \label ],
[ \riseComb5, \slotCtl ],
[ ],
// extras:
// e.g. an editor with more space for controls
[\phatEdit, \extra, { c.makeEdit('Test', 40) } ],
// or maybe bigger buttons play, end buttons
[\play, \extra, { c.playN } ],
[\end, \extra, { c.end(2, true) } ],
]
)
)
///////////////// Possible next extensions ////////////////
* insert new slotNames by name, or remove existing slotnames, keeping the structure consistent;
for reconfiguration of the list of proxychain slots that can be used.
That would require a better gui where the buttons can be updated.
////////////// not done yet //////////
// replace a slot given by name
c.replace(\dust, \noyz, mix -> { |nfreq1=1200| LFDNoise0.ar(nfreq1) });
insertAt(index, name, funcOrAssoc)
inserts in the chain at this index,
replaces if a slot exists there.
c.insertAt(5, \noyz, mix -> { |nfreq2=1200| GrayNoise.ar(nfreq2) });
insertAfter(index, name, funcOrAssoc)
insertBefore(index, name, funcOrAssoc)
inserts after (or before) a given slot - halfway toward the neighbour.
e.g.
c.insertAfter(\dust, \klong, \filter -> { |in, freq=400, att=0.01, decay=0.3, slope=0.8|
Formlet.ar(in, freq * [0.71, 1, 1.4], att, decay * [1/slope, 1, slope]).sum;
});
// after which slot, name, funcOrAssoc;
c.insertBefore(\dust, \klong, \filter -> { |in, freq=400, att=0.01, decay=0.3, slope=0.8|
Formlet.ar(in, freq * [0.71, 1, 1.4], att, decay * [1/slope, 1, slope]).sum;
});