-
Notifications
You must be signed in to change notification settings - Fork 0
/
StereoNodeProxy.sc
275 lines (230 loc) · 8.92 KB
/
StereoNodeProxy.sc
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
/***
Notice of GPL compliance
Much of the code in this file consists of modified versions of code from
the Just-In-Time Library, released under GPLv3 as part of the SuperCollider
package. I needed to override just a couple of critical places, but there
aren't many method hooks to do so.
Full credit to the authors of JITLib: Julian Rohrhuber, Alberto de Campo,
and many others.
I intend these methods as derivative works, released in source form back
to the community, under GPLv3. I am expressly not claiming authorship
for the portions of this code not written by myself.
-- H. James Harkins
***/
StereoNodeProxy : NodeProxy {
defineBus { |rate = \audio, numChannels|
// 2 unless otherwise specified, but only for audio proxies
if(numChannels.isNil and: { rate == \audio }) {
numChannels = 2;
};
^super.defineBus(rate, numChannels)
}
put { | index, obj, channelOffset = 0, extraArgs, now = true |
var container, bundle, oldBus = bus;
if(obj.isNil) { this.removeAt(index); ^this };
if(index.isSequenceableCollection) {
^this.putAll(obj.asArray, index, channelOffset)
};
bundle = MixedBundle.new;
// this is the only thing I am changing
// but 'put' is not well modularized so I have to copy the whole method
container = obj.makeStereoProxyControl(channelOffset, this);
container.build(this, index ? 0); // bus allocation happens here
if(this.shouldAddObject(container, index)) {
// server sync happens here if necessary
if(server.serverRunning) { container.loadToBundle(bundle, server) } { loaded = false; };
this.prepareOtherObjects(bundle, index, oldBus.notNil and: { oldBus !== bus });
} {
format("failed to add % to node proxy: %", obj, this).postln;
^this
};
this.putNewObject(bundle, index, container, extraArgs, now);
this.changed(\source, [obj, index, channelOffset, extraArgs, now]);
}
}
StereoSynthDefControl : SynthDefControl {
build { | proxy, orderIndex = 0 |
var ok, rate, numChannels, outerDefControl, outerBuildProxy, controlNames;
outerDefControl = NodeProxy.buildProxyControl;
outerBuildProxy = NodeProxy.buildProxy;
NodeProxy.buildProxyControl = this;
NodeProxy.buildProxy = proxy;
synthDef = source.buildForStereoProxy(proxy, channelOffset, orderIndex);
NodeProxy.buildProxyControl = outerDefControl;
outerBuildProxy = outerBuildProxy;
rate = synthDef.rate;
numChannels = synthDef.numChannels;
ok = proxy.initBus(rate, numChannels);
if(ok) {
paused = proxy.paused;
canReleaseSynth = synthDef.canReleaseSynth;
canFreeSynth = synthDef.canFreeSynth;
controlNames = synthDef.allControlNames;
hasFadeTimeControl = controlNames.notNil and: {
controlNames.any { |x| x.name === \fadeTime }
};
} {
synthDef = nil;
"synth def couldn't be built".warn;
}
}
}
StereoProxySynthDef : SynthDef {
// pilfered from ProxySynthDef
// I cannot inherit from ProxySynthDef for a whole ton of reasons
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;
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 };
// again, I need to change only this bit
// but this class has absolutely no modular construction at all
// so I have no choice but to copy/paste the entire thing
if(chanConstraint.isNil and: { rate == \audio }) {
chanConstraint = 2;
};
if(chanConstraint.notNil and: { isScalar.not }) {
case
{ chanConstraint < numChannels } {
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;
}
}
{ chanConstraint > numChannels and: { rate == \audio } } {
output = output.asArray.wrapExtend(chanConstraint);
postf("%: expanded % channel% to %\n",
NodeProxy.buildProxy,
numChannels,
if(numChannels == 1, "", "s"),
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 input to control rate\n", NodeProxy.buildProxy);
} {
if(rateConstraint === 'audio') {
output = K2A.ar(output);
rate = 'audio';
postf("%: adopted proxy input to audio rate\n", NodeProxy.buildProxy);
}
}
};
outCtl = Control.names(\out).ir(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
}
}
StereoProxySpace : ProxySpace {
makeProxy {
// kinda ugly and will not work if I have to switch it to standard JITLib later
// It's really not ideal to have a binding between JITLib and my patch GUI.
// But 'defer' means that we have to save the loading state here and pass it back
// using Library to avoid a classvar
var loading = Library.at(\JITModPatch, \nowLoading);
var proxy = StereoNodeProxy.new(server);
this.initProxy(proxy);
// callback; note, we haven't saved the proxy into the space yet
// so we have to wait a tick
// note also that, without the number, 'defer' doesn't defer :-\
{ this.changed(\newProxy, proxy, loading) }.defer(0);
^proxy
}
}
// for disconnection
+ Nil {
<>> { |target, adverb = \in|
target.set(\in, 0); // JITModPatch has logic to break the connection
}
}