remove a crufty folder that's done nothing for quite a while...
[supercollider.git] / build / SCClassLibrary / JITLib / ProxySpace / NodeProxy.sc
blobe560b8c6972c4b971354ecfa44068313bb231bb8
2 BusPlug : AbstractFunction {
3         
4         var <server, <bus;              
5         var <>monitor, <>parentGroup; // if nil, uses default group
6         var busArg; // cache for "/s_new" bus arg
7         var busLoaded = false;
8         
9         classvar <>defaultNumAudio=2, <>defaultNumControl=1;
10         
11         
12         *new { arg server;
13                 ^super.newCopyArgs(server ? Server.default);
14         }
15         
16         *for { arg bus;
17                 bus = bus.asBus;
18                 ^this.new(bus.server).bus_(bus)
19         }
20         
21         *audio { arg server, numChannels;
22                 ^this.new(server).defineBus(\audio, numChannels);
23         }
24         
25         *control { arg server, numChannels;
26                 ^this.new(server).defineBus(\control, numChannels);
27         }
28         
29         clear { 
30                 this.free;
31                 this.stop;
32                 this.freeBus;
33                 monitor = nil;
34         }
35                 
36         
37         ////////  bus definitions  //////////////////////////////////////////
38         
39         
40         
41         bus_ { arg b; 
42                 this.freeBus; 
43                 bus = b; 
44                 this.makeBusArg;
45                 busLoaded = bus.server.serverRunning;
46         }
47         
48         // returns boolean
49         initBus { arg rate, numChannels;
50                                 if(rate.isNil or: { rate === 'scalar' }) { ^true }; // this is no problem
51                                 if(this.isNeutral) {
52                                         this.defineBus(rate, numChannels);
53                                         ^true
54                                 } {
55                                         numChannels = numChannels ? this.numChannels;
56                                         ^(bus.rate === rate) and: { numChannels <= bus.numChannels }
57                                 };
58         }
59         
60         defineBus { arg rate=\audio, numChannels;
61                 if(numChannels.isNil, {
62                         numChannels = if(rate === \audio) { 
63                                                                 this.class.defaultNumAudio 
64                                                         } {
65                                                                 this.class.defaultNumControl                                                    }
66                 });
67                 this.bus = Bus.alloc(rate, server, numChannels);
68                 
69         }
70         
71         
72         freeBus {
73                 if(bus.notNil, {
74                         if(bus.rate === \control) { bus.setAll(0) }; // clean up
75                         bus.free;
76                         monitor.stop;
77                 });
78                 busArg = bus = nil;
79                 busLoaded = false;
80         }
81         
82         busArg { ^busArg ?? { this.makeBusArg } }
83                 
84         makeBusArg {    
85                         var index, numChannels, prefix;
86                         if(bus.isNil) { ^busArg = "" }; // still neutral
87                         prefix = if(this.rate == \audio) { "\a" } { "\c" };
88                         index = this.index;
89                         numChannels = this.numChannels;
90                         ^busArg = if(numChannels == 1) { 
91                                 prefix ++ index
92                         } { 
93                                 { |i| prefix ++ (index + i) }.dup(numChannels)
94                         }
95         }
96         wakeUpToBundle {}
97         wakeUp {}
98         asBus { ^if(this.isNeutral) { nil } { bus } }
99         asNodeArg { ^if(this.isNeutral) { nil } { this.busArg } }
100                 
101         
102         ////////////  playing and access  /////////////////////////////////////////////
103         
104         
105         rate {  ^if(bus.isNil) { \scalar } { bus.rate } }
106         numChannels {  ^if(bus.isNil) { nil } { bus.numChannels } }
107         index { ^if(bus.isNil) { nil } { bus.index } }
108         
109         
110         isNeutral {
111                 ^bus.isNil or: { bus.index.isNil and: { bus.numChannels.isNil } }
112         }
113         
114         prepareOutput { } // see subclass
115         clock { ^nil  }
117         ar { arg numChannels, offset=0;
118                 if(this.isNeutral) { 
119                         this.defineBus(\audio, numChannels) 
120                 };
121                 this.prepareOutput;
122                 ^InBus.ar(bus, numChannels ? bus.numChannels, offset)
123         }
124         
125         kr { arg numChannels, offset=0;
126                 if(this.isNeutral) { 
127                         this.defineBus(\control, numChannels) 
128                 };
129                 this.prepareOutput;
130                 ^InBus.kr(bus, numChannels ? bus.numChannels, offset)
131         }
132         
133         // for now, force multichannel expansion in streams early.
134         embedInStream { arg inval;
135                 ^this.asControlInput.embedInStream(inval);
136         }
137         
138         asControlInput {
139                         if(this.isPlaying.not) {
140                                 if(this.isNeutral) { this.defineBus(\control, 1) }; 
141                                 this.wakeUp
142                         };
143                         ^this.busArg;
144         }
145         asUGenInput {
146                 ^this.value;
147         }
148                 
149         
150         /////  math support  /////////
151         
152         value { arg something; 
153                 var n;
154                 if(UGen.buildSynthDef.isNil) { ^this }; // only return when in ugen graph.
155                 something !? {  n = something.numChannels };
156                 ^if(something.rate == 'audio') { this.ar(n) } { this.kr(n) }  
157         }
158         
159         composeUnaryOp { arg aSelector;
160                 ^UnaryOpPlug.new(aSelector, this)
161         }
162         composeBinaryOp { arg aSelector, something;
163                 ^BinaryOpPlug.new(aSelector, this, something)
164         }
165         reverseComposeBinaryOp { arg aSelector, something;
166                 ^BinaryOpPlug.new(aSelector, something, this)
167         }
168         composeNAryOp { arg aSelector, anArgList;
169                 ^thisMethod.notYetImplemented
170                 //^NAryOpPlug.new(aSelector, [this]++anArgList) // nary op ugens are not yet implemented
171         }
172         
173         
174         
175         ///// monitoring //////////////
176         
177         
178         play { arg out, numChannels, group, multi=false, vol, fadeTime, addAction;  
179                 var bundle = MixedBundle.new;
180                 if(this.homeServer.serverRunning.not) { 
181                         ("server not running:" + this.homeServer).warn; 
182                         ^this 
183                 };
184                 this.playToBundle(bundle, out, numChannels, group, multi, vol, fadeTime, addAction);
185                 // homeServer: multi client support: monitor only locally
186                 bundle.schedSend(this.homeServer, this.clock, this.quant)
187         }
188         
189         playN { arg outs, amps, ins, vol, fadeTime, group, addAction;
190                 var bundle = MixedBundle.new;
191                 if(this.homeServer.serverRunning.not) { 
192                         ("server not running:" + this.homeServer).warn; 
193                         ^this 
194                 };
195                 this.playNToBundle(bundle, outs, amps, ins, vol, fadeTime, group, addAction);
196                 bundle.schedSend(this.homeServer, this.clock, this.quant)
197         }
198         
200         
201         fadeTime { ^0.02 }
202         quant { ^nil }
203         vol { ^if(monitor.isNil) { 1.0 }{ monitor.vol } }
204         vol_ { arg val; this.initMonitor(val) }
206         monitorIndex { ^if(monitor.isNil) { nil }{ monitor.out } }
207         monitorGroup { ^if(monitor.isNil) { nil } { monitor.group } }
208         
209         initMonitor { arg vol;
210                 if(this.rate !== 'audio') { Error("can only monitor audio proxy").throw };
211                 if(monitor.isNil) { monitor = Monitor.new };
212                 if (vol.notNil) { monitor.vol_(vol) };
213                 ^monitor
214         }
215         
216         stop { arg fadeTime=0.1, reset=false; monitor.stop(fadeTime); if(reset) { monitor = nil }; }
217         
218         scope { arg bufsize = 4096, zoom; if(this.isNeutral.not) { ^bus.scope(bufsize, zoom) } }
219         
220         record { arg path, headerFormat="aiff", sampleFormat="int16", numChannels;
221                 var rec;
222                 if(server.serverRunning.not) { "server not running".inform; ^nil };
223                 rec = RecNodeProxy.newFrom(this, numChannels);
224                 rec.open(path, headerFormat, sampleFormat);
225                 rec.record;
226                 ^rec
227         }
228         
229         // shared node proxy support
230         
231         shared { ^false } 
232         homeServer { ^server }
233         
234         printOn { arg stream;
235                 stream  << this.class.name << "." << bus.rate << "(" 
236                                 << server << ", " << bus.numChannels <<")";
237         }
238         
239         
240         // monitor bundling
241         
242         playToBundle { arg bundle, out, numChannels, 
243                                 group, multi=false, vol, fadeTime, addAction;
244                 this.newMonitorToBundle(bundle, numChannels);
245                 group = group ?? { if(parentGroup.isPlaying) { parentGroup } { this.homeServer.asGroup } };
246                 monitor.playToBundle(bundle, bus.index, bus.numChannels, out, numChannels, group, 
247                         multi, vol, fadeTime, addAction);
248         }
249         
250         playNToBundle { arg bundle, outs, amps, ins, vol, fadeTime, group, addAction;
251                 this.newMonitorToBundle(bundle); // todo: numChannels
252                 group = group ?? { if(parentGroup.isPlaying) {parentGroup}{this.homeServer.asGroup} };
253                 monitor.playNBusToBundle(bundle, outs, amps, ins, bus, vol, fadeTime, group, addAction);
254         
255         }
256         
257         newMonitorToBundle { arg bundle, numChannels;
258                 this.initBus(\audio, numChannels);
259                 this.initMonitor;
260                 if(this.isPlaying.not) { this.wakeUpToBundle(bundle) };
261         }
262         
268 //////////////////////////////////////////////////////////////
269 // a bus plug that holds synths or client side players ///////
272 NodeProxy : BusPlug {
275         var <group, <objects, <nodeMap; 
276         var <loaded=false, <>awake=true, <paused=false;
277         var <>clock, <>quant;
278         classvar <>buildProxyControl;
279         
280         
281         *new { arg server, rate, numChannels, inputs;
282                 var res;
283                 res = super.new(server).init;
284                 res.initBus(rate, numChannels);
285                 inputs.do { arg o; res.add(o) };
286                 ^res
287         }
288         
289         init {
290                 nodeMap = ProxyNodeMap.new; 
291                 objects = Order.new;
292                 loaded = false;
293         }
294         
295         clear { arg fadeTime=0;
296                 this.free(fadeTime, true);      // free group and objects
297                 this.removeAll;                         // take out all objects
298                 this.stop(fadeTime, true);              // stop any monitor
299                 monitor = nil;
300                 this.freeBus;    // free the bus from the server allocator 
301                 this.init;      // reset the environment
302                 
303         }
304         
305         end { arg fadeTime, reset=false;
306                 var dt;
307                 dt = fadeTime ? this.fadeTime;
308                 fork { 
309                         this.free(dt, true); 
310                         (dt + (server.latency ? 0)).wait; 
311                         this.stop(0, reset);
312                 }
313         }
314         
315         pause {
316                 if(this.isPlaying) { objects.do { |item| item.pause(clock, quant) } };
317                 paused = true;
318         }
319         
320         resume {
321                 paused = false;
322                 if(this.isPlaying) { objects.do { |item| item.resume(clock, quant) } };
323         }
324         
325         fadeTime_ { arg t;
326                 if(t.isNil) { this.unset(\fadeTime) } { this.set(\fadeTime, t) };
327         }
328         fadeTime {
329                 ^nodeMap.at(\fadeTime).value ? 0.02;
330         }
331         prFadeTime { ^nodeMap.at(\fadeTime).value }
332         
333         asGroup { ^group.asGroup }
334         asTarget { ^group.asGroup }
335         asNodeID { ^group.asNodeID }
336         
337         parentGroup_ {arg node;
338                 if(node.isPlaying.not) { "node not playing and registered: % \n".postf(node); ^this };
339                 parentGroup = node;
340                 if(group.isPlaying) { group.moveToHead(parentGroup) }
341         }
342                 
343                 
344         //////////// set the source to anything that returns a valid ugen input ////////////
345         
346         add { arg obj, channelOffset=0, extraArgs, now = true;
347                 this.put(objects.pos, obj, channelOffset, extraArgs, now)
348         }
349         
350         source_ { arg obj;
351                 this.put(nil, obj, 0)
352         }
353         sources_ { arg list;
354                 this[0..] = list;
355         }
356         
357         source { ^objects.at(0).source }
358         sources {^objects.array.collect(_.source) }
359         prime { arg obj;
360                 this.put(nil, obj, 0, nil, false);
361         }
362         
363         at { arg index; 
364                 "info: node proxy 'at' was changed to return source. " 
365                 "use proxy.objects.at to access control object".postln; 
366                 ^objects.at(index).source 
367         }
368         
369         put { arg index, obj, channelOffset = 0, extraArgs, now = true;                         var container, bundle, orderIndex;
370                         if(obj.isNil) { this.removeAt(index); ^this };
371                         if(index.isSequenceableCollection) {                                            ^this.putAll(obj.asArray, index, channelOffset) 
372                         };
373                         
374                         orderIndex = index ? 0;
375                         container = obj.makeProxyControl(channelOffset, this);
376                         container.build(this, orderIndex); // bus allocation happens here
377                         
378                         if(this.shouldAddObject(container, index)) {
379                                 bundle = MixedBundle.new;
380                                 if(index.isNil) 
381                                         { this.removeAllToBundle(bundle) }
382                                         { this.removeToBundle(bundle, index) };
383                                 objects = objects.put(orderIndex, container);
384                         } {
385                                 format("failed to add % to node proxy: %", obj, this).inform;
386                                 ^this 
387                         };
388                         
389                         if(server.serverRunning) {
390                                 now = awake && now;
391                                 if(now) {
392                                         this.prepareToBundle(nil, bundle);
393                                 };
394                                 container.loadToBundle(bundle, server);
395                                 loaded = true;
396                                 if(now) {
397                                         container.wakeUpParentsToBundle(bundle);
398                                         this.sendObjectToBundle(bundle, container, extraArgs, index);
399                                 };
400                                 bundle.schedSend(server, clock, quant);
401                         } {
402                                 loaded = false;
403                         };
405         }
406         
407         putAll { arg list, index=(0), channelOffset = 0;
408                                 channelOffset = channelOffset.asArray;
409                                 if(index.isSequenceableCollection) {
410                                         max(list.size, index.size).do { |i|
411                                                 this.put(index.wrapAt(i), list.wrapAt(i), channelOffset.wrapAt(i)) 
412                                         } 
413                                 }{
414                                         list.do { |item, i| this.put(i + index, item, channelOffset.wrapAt(i)) }                                }
415         }
416         
417         putSeries { arg first, second, last, value;
418                 last = last ?? { max(1, max(objects.size, value.size)) - 1 };
419                 this.putAll(value.asArray, (first, second..last)) 
420         }
421         
422         shouldAddObject { arg obj; ^obj.readyForPlay } // shared node proxy overrides this
423         
424         removeLast { this.removeAt(objects.indices.last) }
425         removeAll { this.removeAt(nil) }
426         removeAt { arg index;
427                                 var bundle;
428                                 bundle = MixedBundle.new; 
429                                 if(index.isNil) 
430                                         { this.removeAllToBundle(bundle) }
431                                         { this.removeToBundle(bundle, index) };
432                                 bundle.schedSend(server); 
433         }
434         
435         lag { arg ... args;
436                 nodeMap.setRates(args);
437                 this.rebuild;
438         }
439         
440         setRates { arg ... args;
441                 nodeMap.setRates(args);
442                 this.rebuild;
443         }
444                         
445         defineBus { arg rate=\audio, numChannels;
446                 super.defineBus(rate, numChannels);
447                 this.linkNodeMap;
448         }
449         
450         linkNodeMap {
451                 var index;
452                 index = this.index;
453                 if(index.notNil) { nodeMap.set(\out, index, \i_out, index) };
454                 nodeMap.proxy = this;
455         }
456         
457         server_ { arg inServer;
458                 if(this.isNeutral.not) { 
459                         // Error("can't change the server").throw 
460                         this.end;
461                         loaded = false;
462                 };
463                 server = inServer;
464         }
465         
466         bus_ { arg inBus;
467                 if(server != inBus.server) { Error("can't change the server").throw };
468                 super.bus_(inBus);
469                 this.linkNodeMap;
470                 this.rebuild;
471         }
472                 
473         group_ { arg agroup;
474                 var bundle;
475                 if(agroup.server !== server, { Error("cannot move to another server").throw });
476                 NodeWatcher.register(agroup.isPlaying_(true)); // assume it is playing
477                 if(this.isPlaying)
478                 {       bundle = MixedBundle.new;
479                         this.stopAllToBundle(bundle); 
480                         group = agroup;
481                         this.sendAllToBundle(bundle); 
482                         bundle.schedSend(server, clock, 0.0);
483                 } { group = agroup };
484         }
485         
486         // applying the settings to nodes //
487         
488         nodeMap_ { arg map;
489                 this.setNodeMap(map, false)
490         }
491         
492         // unsetArgsToBundle does not work.
493         setNodeMap { arg map, xfade=true;
494                 var bundle, old, fadeTime;
495                 map.set(\fadeTime, this.fadeTime); // keep old fadeTime
496                 bundle = MixedBundle.new;
497                 old = nodeMap;
498                 nodeMap = map;
499                 this.linkNodeMap;
500                 if(this.isPlaying) {
501                         if(xfade) { this.sendEach(nil,true) }
502                         {
503                         this.unsetToBundle(bundle); // unmap old
504                         nodeMap.addToBundle(bundle, group); // map new
505                         bundle.schedSend(server, clock, quant);
506                         }
507                 };
508         }
509         
510         
511         rebuild {
512                 var bundle;
513                 if(this.isPlaying) {
514                         bundle = MixedBundle.new;
515                         this.stopAllToBundle(bundle);
516                         bundle.schedSend(server, clock, quant);
517                         bundle = MixedBundle.new;
518                         loaded = false;
519                         this.loadToBundle(bundle);
520                         this.sendAllToBundle(bundle);
521                         bundle.schedSend(server, clock, quant);
522                 } {
523                         loaded = false;
524                 };
525         
526         }
527         
528         controlNames { arg except = #[\out, \i_out, \gate, \fadeTime];
529                 var all = Array.new; // Set doesn't work, because equality undefined for ControlName
530                 objects.do { |el|
531                         el.controlNames.do { |item|
532                                 if(except.includes(item.name).not and: {
533                                         all.every { |cn| cn.name !== item.name }
534                                 }) {
535                                         all = all.add(item);
536                                 }
537                         };
538                 };
539                 ^all
540         }
542         controlKeys { |except, noInternalKeys = true|
543                 var list = Array.new;
544                         if (noInternalKeys) { except = except ++ this.internalKeys; };
545                         this.controlNames.do { |el, i|
546                                 if(except.includes(el.name).not)
547                                 { list = list.add(el.name) }
548                         }
549                 ^list
550         }
551         internalKeys {
552                 ^#[\out, \i_out, \gate, \fadeTime];
553         }
554         getKeysValues { |keys, except, withDefaults = true, noInternalKeys = true|
555                 var pairs, result = [], myKeys, defaults, mapSettings;
556                 if (noInternalKeys) { except = except ++ this.internalKeys; };
558                 pairs = this.controlKeysValues(keys, except).clump(2);
559                 #myKeys, defaults = pairs.flop;
561                 mapSettings = nodeMap.settings;
562                 myKeys.collect { |key, i|
563                         var val, doAdd;
564                         val = mapSettings[key];
565                         doAdd = withDefaults or: val.notNil;
566                         if (doAdd) {
567                                 result = result.add([ key, (val ? defaults[i]).value ]);
568                         };
569                 }
570                 ^result
571         }
573         // controlPairs, gets default values
574         controlKeysValues { arg keys, except = #[\out, \i_out, \gate, \fadeTime];
575                 var list, fullList = this.controlNames(except);
576                 if(keys.isNil or: { keys.isEmpty }) {
577                         list = Array(fullList.size * 2);
578                         fullList.do { |cn| list.add(cn.name).add(cn.defaultValue) }
579                 } {
580                         list = Array(keys.size * 2);
581                         keys.do { |key|
582                                 var val = fullList.detect { |cn| cn.name == key };
583                                 val = if(val.isNil) { 0 } { val.defaultValue };
584                                 list.add(key).add(val)
585                         }
586                 }
587                 ^list;
588         }
589         
590         // derive names and default args from synthDefs
591         supplementNodeMap { arg keys, replaceOldKeys=false;
592                 this.controlNames.do { |el|
593                                         var key;
594                                         key = el.name; 
595                                         if (
596                                                 ( replaceOldKeys or: { nodeMap.at(key).isNil } )
597                                                 and: 
598                                                 { keys.isNil or: { keys.includes(key) } }
599                                         ) { nodeMap.set(key, el.defaultValue) }
600                 }
601         }
602         
603         
604         generateUniqueName {
605                         ^server.clientID.asString ++ this.identityHash.abs
606         }
610         /////////// filtering within one proxy /////////////////
611         
612         filter { arg i, func; this.put(i, \filter -> func) }
613         
614         /////////// shortcuts for efficient bus input //////////////
615         
616         readFromBus { arg busses;
617                 var n, x;
618                 busses = busses.asCollection;
619                 n = this.numChannels;
620                 busses.do { arg item, i;
621                         x = min(item.numChannels ? n, n);
622                         this.put(i,
623                                 SynthControl.new("system_link_" ++ this.rate ++ "_" ++ x), 
624                                 0, 
625                                 ["in", item.index, "out", this.index]
626                         )
627                 };
628         }
629         
630         read { arg proxies;
631                 proxies = proxies.asCollection;
632                 proxies.do { arg item; item.wakeUp };
633                 this.readFromBus(proxies)
634         }
635         
636         
637         /////// send and spawn //////////
638         
639         
640         getBundle {
641                 var bundle;
642                 bundle =        MixedBundle.new; 
643                 this.prepareToBundle(nil, bundle);
644                 ^bundle
645         }
646                 
647         spawn { arg extraArgs, index=0;
648                         var bundle, obj, i;
649                         obj = objects.at(index);
650                         if(obj.notNil) {
651                                 i = this.index;
652                                 bundle = this.getBundle;
653                                 obj.spawnToBundle(bundle, extraArgs, this);
654                                 nodeMap.addToBundle(bundle, -1);
655                                 bundle.schedSend(server);
656                         }
657         }
658         
659         
660         send { arg extraArgs, index, freeLast=true;
661                         var bundle, obj;
662                         if(objects.isEmpty) { ^this };
663                         if(index.isNil) { 
664                                 bundle = this.getBundle;
665                                 if(freeLast) { this.stopAllToBundle(bundle) };
666                                 this.sendAllToBundle(bundle, extraArgs);
667                                 bundle.schedSend(server);
668                         
669                         } {
670                                 obj = objects.at(index);
671                                 if(obj.notNil) {
672                                         bundle = this.getBundle;
673                                         if(freeLast) { obj.stopToBundle(bundle) };
674                                         
675                                         this.sendObjectToBundle(bundle, obj, extraArgs, index);
676                                         bundle.schedSend(server);
677                                 }
678                         }
679         }
680         
681         sendAll { arg extraArgs, freeLast=true;
682                 this.send(extraArgs, nil, freeLast);
683         }
684         
685         sendEach { arg extraArgs, freeLast=true;
686                         var bundle;
687                         bundle = this.getBundle;
688                         if(freeLast, { this.stopAllToBundle(bundle) });
689                         this.sendEachToBundle(bundle, extraArgs);
690                         bundle.schedSend(server);
691         
692         }
693         
694         quantize { arg ... proxies;
695                 var quant = this.quant ? 1.0;
696                 ([this]++proxies).do { |x|
697                         x.quant = quant;
698                         x.send;
699                 }
700         }
701         
702         wakeUp {        if(this.isPlaying.not) { this.deepWakeUp } } // do not touch internal state if playing
703         
704         deepWakeUp {
705                                 var bundle;
706                                 bundle = MixedBundle.new;
707                                 this.wakeUpToBundle(bundle);
708                                 bundle.schedSend(server, clock, quant)
709         }
710                 
711                 
712         /////// append to bundle commands
713         
714         
715         defaultGroupID { ^server.nextNodeID } //shared proxy support
716         
717         prepareToBundle { arg argGroup, bundle, addAction=\addToTail;
718                 if(this.isPlaying.not) {
719                                 group = Group.basicNew(server, this.defaultGroupID);
720                                 NodeWatcher.register(group);
721                                 group.isPlaying = server.serverRunning;
722                                 if(argGroup.isNil and: { parentGroup.isPlaying }) { argGroup = parentGroup };
723                                 bundle.addPrepare(group.newMsg(argGroup ?? { server.asGroup }, addAction));
724                 }
725         }
727         //apply the node map settings to the entire group
728         sendAllToBundle { arg bundle, extraArgs;
729                                 objects.do { arg item;
730                                         item.playToBundle(bundle, extraArgs.value, this);
731                                 };
732                                 if(objects.notEmpty) { nodeMap.addToBundle(bundle, group) };
733         }
734         
735         //apply the node map settings to each synth separately
736         sendEachToBundle { arg bundle, extraArgs;
737                                 objects.do { arg item;
738                                         this.sendObjectToBundle(bundle, item, extraArgs.value)
739                                 };
740         }
741         
742         //send single object
743         sendObjectToBundle { arg bundle, object, extraArgs, index;
744                                 var synthID, target, nodes;
745                                 synthID = object.playToBundle(bundle, extraArgs.value, this);
746                                 if(synthID.notNil) {
747                                         if(index.notNil and: { objects.size > 1 }) { // if nil, all are sent anyway
748                                                 // make list of nodeIDs following the index
749                                                 nodes = Array(4);
750                                                 objects.doRange({ arg obj; 
751                                                         var id = obj.nodeID;
752                                                         if(id.notNil and: { id != synthID }) { nodes = nodes ++ id ++ synthID };
753                                                 }, index + 1);
754                                                 if(nodes.size > 0) { bundle.add(["/n_before"] ++ nodes.reverse) };
755                                         };
756                                 nodeMap.addToBundle(bundle, synthID)
757                                 };
758         }
759         
760         removeToBundle { arg bundle, index;
761                 var obj, dt, playing;
762                 playing = this.isPlaying;
763                 obj = objects.removeAt(index);
764                 
765                 if(obj.notNil) { 
766                                 dt = this.fadeTime;
767                                 if(playing) { obj.stopToBundle(bundle, dt) };
768                                 obj.freeToBundle(bundle, dt);
769                 };
770                 
771         }
772         
773         removeAllToBundle { arg bundle;
774                 var dt, playing;
775                 dt = this.fadeTime;
776                 playing = this.isPlaying;
777                 objects.do { arg obj; 
778                                 if(playing) { obj.stopToBundle(bundle, dt) }; 
779                                 obj.freeToBundle(bundle, dt);
780                 };
781                 objects.makeEmpty;
782         }
783         
784         stopAllToBundle { arg bundle;
785                 var obj, dt;
786                 dt = this.fadeTime;
787                 if(this.isPlaying) { objects.do { arg obj; obj.stopToBundle(bundle, dt) } };
788         }
789         
790         reallocBusIfNeeded { // bus is reallocated only if the server was not booted on creation.
791                 if(busLoaded.not and: { bus.notNil }) {
792                         bus.realloc; 
793                         this.linkNodeMap
794                 }
795         }
796         
797         loadToBundle { arg bundle;
798                 this.reallocBusIfNeeded;
799                 objects.do { arg item, i;
800                         item.build(this, i);
801                         item.loadToBundle(bundle, server);
802                 };
803                 loaded = true;
804         }
806         
807         wakeUpToBundle { arg bundle, checkedAlready;
808                 if(checkedAlready.isNil) { checkedAlready = IdentitySet.new };
809                 if(checkedAlready.includes(this).not) {
810                         checkedAlready.add(this);
811                         this.wakeUpParentsToBundle(bundle, checkedAlready);
812                         if(loaded.not) { this.loadToBundle(bundle) };
813                         if(awake and: { this.isPlaying.not }) { 
814                                 this.prepareToBundle(nil, bundle, \addToHead);
815                                 this.sendAllToBundle(bundle)
816                         };
817                 };
818                 
819         }
820         
821         wakeUpParentsToBundle { arg bundle, checkedAlready;
822                         nodeMap.wakeUpParentsToBundle(bundle, checkedAlready);
823                         objects.do{ arg item; item.wakeUpParentsToBundle(bundle, checkedAlready) };
824         }
825                 
826         // used in 'garbage collector'
827         
828         getFamily { arg set, alreadyAsked;
829                 var parents;
830                 parents = IdentitySet.new;
831                 alreadyAsked = alreadyAsked ?? { IdentitySet.new };
832                 if(alreadyAsked.includes(this).not) {
833                         alreadyAsked.add(this);
834                         objects.do { arg obj; parents.addAll(obj.parents) };
835                         parents.addAll(nodeMap.parents);
836                         parents.do { arg proxy; proxy.getFamily(parents, alreadyAsked) };
837                         set.add(this);
838                         set.addAll(parents);
839                 };
840                 ^set    
841         }
842         
843         getStructure { arg alreadyAsked;
844                 var parents, substructure;
845                 parents = List.new;
846                 alreadyAsked = alreadyAsked ?? { IdentitySet.new };
847                 if(alreadyAsked.includes(this).not) {
848                         alreadyAsked.add(this);
849                         objects.do { arg obj; parents.addAll(obj.parents) };
850                         parents.addAll(nodeMap.parents);
851                         substructure = parents.collect { arg proxy; proxy.getStructure(alreadyAsked) };
852                         ^[this, substructure.flatten(1)];
853                 };
854                 ^nil
855         }
856         
857         
858                 
859         
860         ////// private /////
861         
862         
863         prepareOutput {
864                 var parentPlaying;
865                 parentPlaying = this.addToChild;
866                 if(parentPlaying) { this.deepWakeUp };
867         }
868         
869         addToChild {
870                 var child;
871                 child = buildProxyControl;
872                 if(child.notNil) { child.addParent(this) };
873                 ^child.isPlaying;
874         }
876         
877         
878         ////////////behave like my group////////////
879         
880         isPlaying { ^group.isPlaying }
881         
882         free { arg fadeTime, freeGroup = true;
883                 var bundle;
884                 if(this.isPlaying) {    
885                         bundle = MixedBundle.new;
886                         if(fadeTime.notNil) { bundle.add([15, group.nodeID, "fadeTime", fadeTime]) };
887                         this.stopAllToBundle(bundle);
888                         if(freeGroup) { 
889                                 bundle.sched((fadeTime ? this.fadeTime) + (server.latency ? 0), { group.free }); 
890                         };
891                         bundle.send(server);
892                 }
893         }
894         
895         release { arg fadeTime; this.free(fadeTime, false) }
896         
897         
898         set { arg ... args; // pairs of keys or indices and value
899                 nodeMap.set(*args);
900                 if(this.isPlaying) { 
901                         server.sendBundle(server.latency, [15, group.nodeID] ++ args); 
902                 };
903         }
904         
905         setn { arg ... args;
906                 nodeMap.set(*args);
907                 if(this.isPlaying) { 
908                         server.sendBundle(server.latency, group.setnMsg(*args)); 
909                 };
910         }
911         
912         setGroup { arg args, useLatency=false;
913                 if(this.isPlaying) { 
914                         server.sendBundle(if(useLatency) { server.latency }, [15, group.nodeID] ++ args); 
915                 };
916         }
917         
918         // map to a control proxy
919                 
920         map { arg ... args; // key, proxy ... args; 
921                 var bundle;
922                 if(this.isPlaying) {
923                         bundle = List.new;
924                         nodeMap.unmapArgsToBundle(bundle, group.nodeID, args[0,2..args.size-2]);
925                         nodeMap.map(*args).updateBundle;
926                         nodeMap.addToBundle(bundle, group.nodeID);
927                         server.listSendBundle(server.latency, bundle);
928                 } { nodeMap.map(*args) }
929         }
930         
931         mapn { arg ... args; // for now, avoid errors.
932                 ^this.map(*args)
933         }
934         
935         // map to current environment
936         mapEnvir { arg ... keys;
937                 nodeMap.mapEnvir(*keys);
938                 if(this.isPlaying) { 
939                         nodeMap.sendToNode(group);
940                 }
941         }
942         
943                 
944         unset { arg ... keys;
945                 var bundle = List.new;
946                 this.unsetToBundle(bundle, keys);
947                 if(bundle.notEmpty) {
948                         server.listSendBundle(server.latency, bundle)
949                 }
950         }
951         
952         
953         unmap { arg ... keys;
954                 var bundle;
955                 if(keys.isEmpty) { keys = nodeMap.mappingKeys; if(keys.isEmpty) { ^this } };
956                 if(this.isPlaying) {
957                         bundle = List.new;
958                         nodeMap.unmapArgsToBundle(bundle, group.nodeID, keys);
959                         if(bundle.notEmpty) { server.listSendBundle(server.latency, bundle) };
960                 };
961                 nodeMap.unmap(*keys);
962         }
963         
964         unsetToBundle { arg bundle, keys;
965                 var pairs = this.controlKeysValues(keys);
966                 if(this.isPlaying) {
967                         if(pairs.notEmpty) {
968                                 bundle.add([15, group.nodeID] ++ pairs);
969                         };
970                 };
971                 nodeMap.unset(*pairs[0,2..]);
972         }
974         
975         // xfades
976         
977         xset { arg ... args;
978                 this.xFadePerform(\set, args);
979         }
980         xmap { arg ... args;
981                 this.xFadePerform(\map, args);
982         }
983         xsetn { arg ... args;
984                 this.xFadePerform(\setn, args);
985         }
986         xmapn { arg ... args;
987                 this.xFadePerform(\map, args); // for now, avoid errors.
988         }
989         xunset { arg ... args;
990                 this.xFadePerform(\unset, args);
991         }
993         xFadePerform { arg selector, args;
994                 var bundle;
995                 nodeMap.performList(selector, args);
996                 if(this.isPlaying) 
997                 { this.sendEach(nil, true) } 
998                 { "not playing".inform }
999         }       
1000         
1001         <-- { arg proxy;
1002                 var bundle = MixedBundle.new;
1003                 this.source = proxy;
1004                 
1005                 if(proxy.monitorGroup.isPlaying) {
1006                         proxy.stop(fadeTime: 0.5);
1007                         if(this.monitorGroup.isPlaying.not) { 
1008                                 this.playToBundle(bundle, fadeTime:0.1) 
1009                         }
1010                 };
1011                 bundle.add(proxy.moveBeforeMsg(this));
1012                 bundle.send(server, server.latency);
1013         }
1014         
1015         moveBeforeMsg { arg ... proxies;
1016                 var list;
1017                 ([this] ++ proxies).do { |el|
1018                         if(el.isPlaying) { 
1019                                 list = list.add(el.group);
1020                                 if(el.monitor.isPlaying) {
1021                                         list = list.add(el.monitor.group) // debatable. maybe check whether special
1022                                 }
1023                         }
1024                 };
1025                 ^list !? { Node.orderNodesMsg(list) }
1026         }
1027         
1028         orderNodes { arg ... proxies;
1029                 var msg = this.moveBeforeMsg(*proxies);
1030                 msg !? {
1031                         server.sendBundle(nil, msg)
1032                 }
1033         }
1038 Ndef : NodeProxy {
1039         classvar <>defaultServer, <>all;
1040         var <>key;
1041         
1042         *initClass { all = () }
1043         
1044         *new { arg key, object; 
1045                 // key may be simply a symbol, or an association of a symbol and a server name
1046                 var res, server, dict;
1047                 
1048                 if(key.isKindOf(Association)) {
1049                         server = Server.named.at(key.value);
1050                         if(server.isNil) { 
1051                                 Error("Ndef(%): no server found with this name.".format(key)).throw 
1052                         };
1053                         key = key.key;
1054                 } {
1055                         server = Server.default;
1056                 };
1057                 
1058                 dict = this.dictFor(server);
1059                 res = dict.envir.at(key);
1060                 if(res.isNil) { 
1061                         res = super.new(server).key_(key); 
1062                         dict.envir.put(key, res) 
1063                 };
1064                 
1065                 object !? { res.source = object };
1066                 ^res;
1067         }
1068         
1069         *ar { arg key, numChannels, offset=0;
1070                 ^this.new(key).ar(numChannels, offset)
1071         }
1072         
1073         *kr { arg key, numChannels, offset=0;
1074                 ^this.new(key).kr(numChannels, offset)
1075         }
1076         
1077         *dictFor { arg server;
1078                 var dict = all.at(server.name);
1079                 if(dict.isNil) {
1080                         dict = ProxySpace.new(server); // use a proxyspace for ease of access.
1081                         all.put(server.name, dict) 
1082                 };
1083                 ^dict
1084         }
1085         
1086         *clear {
1087                 all.do { arg dict; dict.do { arg item; item.clear } };
1088                 all.clear;
1089         }
1090                                 
1091         storeOn { arg stream;
1092                 this.printOn(stream);
1093         }
1094         printOn { arg stream;
1095                 stream << this.class.name << "(" <<< this.key << ")"
1096         }