1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
|
ProxySynthDef : SynthDef {
var <>rate, <>numChannels;
var <>canReleaseSynth, <>canFreeSynth;
classvar <>sampleAccurate=false;
*new { | name, func, rates, prependArgs, makeFadeEnv = true, channelOffset = 0,
chanConstraint, rateConstraint |
var def, rate, numChannels, output, isScalar, envgen, canFree, hasOwnGate;
var hasGateArg=false, hasOutArg=false;
var outerBuildSynthDef = UGen.buildSynthDef;
def = super.new(name, {
var out, outCtl;
// build the controls from args
output = SynthDef.wrap(func, rates, prependArgs);
output = output.asUGenInput;
// protect from user error
if(output.isKindOf(UGen) and: { output.synthDef != UGen.buildSynthDef }) {
Error("Cannot share UGens between NodeProxies:" + output).throw
};
// protect from accidentally returning wrong array shapes
if(output.containsSeqColl) {
// try first unbubble singletons, these are ok
output = output.collect { |each| each.unbubble };
// otherwise flatten, but warn
if(output.containsSeqColl) {
"Synth output should be a flat array.\n%\nFlattened to: %\n"
"See NodeProxy helpfile:routing\n\n".format(output, output.flat).warn;
output = output.flat;
};
};
output = output ? 0.0;
// determine rate and numChannels of ugen func
numChannels = output.numChannels;
rate = output.rate; // for an array, this will always result in the highest rate
isScalar = rate === 'scalar';
// check for out key. this is used by internal control.
func.def.argNames.do { arg name;
if(name === \out) { hasOutArg = true };
if(name === \gate) { hasGateArg = true };
};
if(isScalar.not and: hasOutArg)
{
"out argument is provided internally!".error; // avoid overriding generated out
^nil
};
// rate is only scalar if output was nil or if it was directly produced by an out ugen
// this allows us to conveniently write constant numbers to a bus from the synth
// if you want the synth to write nothing, return nil from the UGen function.
if(isScalar and: { output.notNil } and: { UGen.buildSynthDef.children.last.isKindOf(AbstractOut).not }) {
rate = 'control';
isScalar = false;
};
//detect inner gates
canFree = UGen.buildSynthDef.children.canFreeSynth;
hasOwnGate = UGen.buildSynthDef.hasGateControl;
makeFadeEnv = if(hasOwnGate and: { canFree.not }) {
"The gate control should be able to free the synth!\n%".format(func).warn; false
} {
makeFadeEnv and: { (isScalar || canFree).not };
};
hasOwnGate = canFree && hasOwnGate; //only counts when it can actually free synth.
if(hasOwnGate.not && hasGateArg) {
"supplied gate overrides inner gate.".error;
^nil
};
//"gate detection:".postln;
//[\makeFadeEnv, makeFadeEnv, \canFree, canFree, \hasOwnGate, hasOwnGate].debug;
// constrain the output to the right number of channels if supplied
// if control rate, no channel wrapping is applied
// and wrap it in a fade envelope
envgen = if(makeFadeEnv) {
EnvGate(i_level: 0, doneAction:2, curve: if(rate === 'audio') { 'sin' } { 'lin' })
} { 1.0 };
if(chanConstraint.notNil
and: { chanConstraint < numChannels }
and: { isScalar.not },
{
if(rate === 'audio') {
postf("%: wrapped channels from % to % channels\n", NodeProxy.buildProxy, numChannels, chanConstraint);
output = NumChannels.ar(output, chanConstraint, true);
numChannels = chanConstraint;
} {
postf("%: kept first % channels from % channel input\n", NodeProxy.buildProxy, chanConstraint, numChannels);
output = output.keep(chanConstraint);
numChannels = chanConstraint;
}
});
output = output * envgen;
//"passed in rate: % output rate: %\n".postf(rateConstraint, rate);
if(isScalar, {
output
}, {
// rate adaption. \scalar proxy means neutral
if(rateConstraint.notNil and: { rateConstraint != \scalar and: { rateConstraint !== rate }}) {
if(rate === 'audio') {
output = A2K.kr(output);
rate = 'control';
postf("%: adopted proxy output signal to control rate\n", NodeProxy.buildProxy);
} {
if(rateConstraint === 'audio') {
output = K2A.ar(output);
rate = 'audio';
postf("%: adopted proxy output signal to audio rate\n", NodeProxy.buildProxy);
}
}
};
// because Out.ar will not accept a control rate ugen, we adapt any other rates
if(rate == 'audio') {
output.do { |x, i|
if(x.rate != rate) {
postf("%: adopted proxy output signal at index % to audio rate:\n%\n", NodeProxy.buildProxy, i, x);
output[i] = K2A.ar(x) // for efficiency in large outputs, we modify in place
};
};
};
outCtl = NamedControl.ir(\out, 0) + channelOffset;
(if(rate === \audio and: { sampleAccurate }) { OffsetOut } { Out }).multiNewList([rate, outCtl] ++ output)
})
});
UGen.buildSynthDef = outerBuildSynthDef;
// set the synthDefs instvars, so they can be used later
def.rate = rate;
def.numChannels = numChannels;
def.canReleaseSynth = makeFadeEnv || hasOwnGate;
def.canFreeSynth = def.canReleaseSynth || canFree;
//[\defcanReleaseSynth, def.canReleaseSynth, \defcanFreeSynth, def.canFreeSynth].debug;
^def
}
}
|