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.





*add(name, func, name, func, ... ) add 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(numChannels, slotNames, server)

numChannels - of the proxy that is created automatically.

slotNames - define which functions from ProxyChain.allSources will be available in what order.

// they can be sources (func, \mix -> func) 

// or filters (\filter -> func, \filterIn -> func)

c = ProxyChain(q.numChans, [\dust, \ringmod, \dist, \riseComb5, \test]);

c.play; // play the proxy

e = c.makeEdit(\chaintest, 12); // make a NodeProxyEditor for it


// passed through to proxy;

c.play;

c.stop;

c.playN;

c.end(2);


*from(proxy, slotNames)

make a proxychain from an existing proxy.

Ndef(\bla).ar(2);

c = ProxyChain.from(Ndef(\bla), [\dust, \ringmod, \dist, \riseComb5, \test]);

Ndef(\bla).play;

Ndef(\bla).clear;



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);


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);

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.

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(\PxChain, nil, 20);


// if specified, can be friendlier

(

g = c.gui(\PxChain, 

[ 

[ \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 } ],

], 

16

)

)


///////////////// 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 that 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;

});