class library: Reset has been renamed to OnError
[supercollider.git] / SCClassLibrary / JITLib / ProxySpace / NodeProxy.sc
blob34e956c3c1365858fb6c4c910ae540160a446cd1
1 NodeProxy : BusPlug {
3         var <group, <objects, <nodeMap;
4         var <loaded=false, <>awake=true, <paused=false;
5         var <>clock, <>quant;
6         classvar <>buildProxyControl;
9         *new { | server, rate, numChannels, inputs |
10                 var res = super.new(server).init;
11                 res.initBus(rate, numChannels);
12                 inputs.do { |o| res.add(o) };
13                 ^res
14         }
16         init {
17                 nodeMap = ProxyNodeMap.new;
18                 objects = Order.new;
19                 loaded = false;
20         }
22         clear { | fadeTime = 0 |
23                 this.free(fadeTime, true);      // free group and objects
24                 this.removeAll;                         // take out all objects
25                 this.stop(fadeTime, true);              // stop any monitor
26                 monitor = nil;
27                 this.freeBus;    // free the bus from the server allocator
28                 this.init;      // reset the environment
29         }
31         end { | fadeTime, reset = false |
32                 var dt = fadeTime ? this.fadeTime;
33                 fork {
34                         this.free(dt, true);
35                         (dt + (server.latency ? 0)).wait;
36                         this.stop(0, reset);
37                 }
38         }
40         isPlaying { ^group.isPlaying }
42         free { | fadeTime, freeGroup = true |
43                 var bundle;
44                 if(this.isPlaying) {
45                         bundle = MixedBundle.new;
46                         if(fadeTime.notNil) { bundle.add([15, group.nodeID, "fadeTime", fadeTime]) };
47                         this.stopAllToBundle(bundle, fadeTime);
48                         if(freeGroup) {
49                                 bundle.sched((fadeTime ? this.fadeTime) + (server.latency ? 0), { group.free });
50                         };
51                         bundle.send(server);
52                 }
53         }
55         release { | fadeTime |
56                 this.free(fadeTime, false)
57         }
59         pause {
60                 if(this.isPlaying) { objects.do { |item| item.pause(clock, quant) } };
61                 paused = true;
62         }
64         resume {
65                 paused = false;
66                 if(this.isPlaying) { objects.do { |item| item.resume(clock, quant) } };
67         }
69         fadeTime_ { | dur |
70                 if(dur.isNil) { this.unset(\fadeTime) } { this.set(\fadeTime, dur) };
71         }
73         fadeTime {
74                 ^nodeMap.at(\fadeTime).value ? 0.02;
75         }
77         prFadeTime { ^nodeMap.at(\fadeTime).value }
79         asGroup { ^group.asGroup }
80         asTarget { ^group.asGroup }
81         asNodeID { ^group.asNodeID }
83         parentGroup_ { | node |
84                 if(node.isPlaying.not) { "node not playing and registered: % \n".postf(node); ^this };
85                 parentGroup = node;
86                 if(group.isPlaying) { group.moveToHead(parentGroup) }
87         }
92         // setting the source
94         source_ { | obj |
95                 this.put(nil, obj, 0)
96         }
98         prime { | obj |
99                 this.put(nil, obj, 0, nil, false);
100         }
102         sources_ { | list |
103                 this[0..] = list;
104         }
106         source { ^objects.at(0).source }
107         sources { ^objects.array.collect(_.source) }
109         add { | obj, channelOffset = 0, extraArgs, now = true |
110                 this.put(objects.pos, obj, channelOffset, extraArgs, now)
111         }
113         at { | index |
114                 ^objects.at(index).source
115         }
117         put { | index, obj, channelOffset = 0, extraArgs, now = true |                  var container, bundle, orderIndex;
118                         if(obj.isNil) { this.removeAt(index); ^this };
119                         if(index.isSequenceableCollection) {                                            ^this.putAll(obj.asArray, index, channelOffset)
120                         };
122                         orderIndex = index ? 0;
123                         container = obj.makeProxyControl(channelOffset, this);
124                         container.build(this, orderIndex); // bus allocation happens here
126                         if(this.shouldAddObject(container, index)) {
127                                 bundle = MixedBundle.new;
128                                 if(index.isNil)
129                                         { this.removeAllToBundle(bundle) }
130                                         { this.removeToBundle(bundle, index) };
131                                 objects = objects.put(orderIndex, container);
132                         } {
133                                 format("failed to add % to node proxy: %", obj, this).inform;
134                                 ^this
135                         };
137                         if(server.serverRunning) {
138                                 now = awake && now;
139                                 if(now) {
140                                         this.prepareToBundle(nil, bundle);
141                                 };
142                                 container.loadToBundle(bundle, server);
143                                 loaded = true;
144                                 if(now) {
145                                         container.wakeUpParentsToBundle(bundle);
146                                         this.sendObjectToBundle(bundle, container, extraArgs, index);
147                                 };
148                                 nodeMap.wakeUpParentsToBundle(bundle);
149                                 bundle.schedSend(server, clock ? TempoClock.default, quant);
150                         } {
151                                 loaded = false;
152                         }
154         }
156         putAll { | list, index = (0), channelOffset = 0 |
157                                 channelOffset = channelOffset.asArray;
158                                 if(index.isSequenceableCollection) {
159                                         max(list.size, index.size).do { |i|
160                                                 this.put(index.wrapAt(i), list.wrapAt(i), channelOffset.wrapAt(i))
161                                         }
162                                 }{
163                                         list.do { |item, i| this.put(i + index, item, channelOffset.wrapAt(i)) }                                }
164         }
166         putSeries { | first, second, last, value |
167                 last = last ?? { max(1, max(objects.size, value.size)) - 1 };
168                 this.putAll(value.asArray, (first, second..last))
169         }
171         filter { | i, func |
172                 this.put(i, \filter -> func)
173         }
175         removeFirst { | fadeTime | this.removeAt(objects.indices.first, fadeTime) }
176         removeLast { | fadeTime | this.removeAt(objects.indices.last, fadeTime) }
177         removeAll { | fadeTime | this.removeAt(nil, fadeTime) }
178         removeAt { | index, fadeTime |
179                 var bundle = MixedBundle.new;
180                 if(index.isNil)
181                         { this.removeAllToBundle(bundle, fadeTime) }
182                         { this.removeToBundle(bundle, index, fadeTime) };
183                 bundle.schedSend(server);
184         }
186         rebuild {
187                 var bundle;
188                 if(this.isPlaying) {
189                         bundle = MixedBundle.new;
190                         this.stopAllToBundle(bundle);
191                         bundle.schedSend(server, clock ? TempoClock.default, quant);
192                         bundle = MixedBundle.new;
193                         loaded = false;
194                         this.loadToBundle(bundle);
195                         this.sendAllToBundle(bundle);
196                         bundle.schedSend(server, clock ? TempoClock.default, quant);
197                 } {
198                         loaded = false;
199                 };
201         }
203         lag { | ... args |
204                 nodeMap.setRates(args);
205                 this.rebuild;
206         }
208         setRates { | ... args |
209                 nodeMap.setRates(args);
210                 this.rebuild;
211         }
213         server_ { | inServer |
214                 if(this.isNeutral.not) {
215                         this.end;
216                         loaded = false;
217                 };
218                 server = inServer;
219         }
221         bus_ { | inBus |
222                 if(server != inBus.server) { Error("can't change the server").throw };
223                 super.bus_(inBus);
224                 this.linkNodeMap;
225                 this.rebuild;
226         }
228         group_ { | inGroup |
229                 var bundle;
230                 if(inGroup.server !== server, { Error("cannot move to another server").throw });
231                 NodeWatcher.register(inGroup.isPlaying_(true)); // assume it is playing
232                 if(this.isPlaying)
233                 {       bundle = MixedBundle.new;
234                         this.stopAllToBundle(bundle);
235                         group = inGroup;
236                         this.sendAllToBundle(bundle);
237                         bundle.schedSend(server, clock ? TempoClock.default, 0.0);
238                 } { group = inGroup };
239         }
242         read { | proxies |
243                 proxies = proxies.asCollection;
244                 proxies.do { arg item; item.wakeUp };
245                 this.readFromBus(proxies)
246         }
248         readFromBus { | busses |
249                 var n, x;
250                 busses = busses.asCollection;
251                 n = this.numChannels;
252                 busses.do { arg item, i;
253                         x = min(item.numChannels ? n, n);
254                         this.put(i,
255                                 SynthControl.new("system_link_" ++ this.rate ++ "_" ++ x),
256                                 0,
257                                 ["in", item.index, "out", this.index]
258                         )
259                 };
260         }
267         // modifying context, setting controls
269         set { | ... args | // pairs of keys or indices and value
270                 nodeMap.set(*args);
271                 if(this.isPlaying) {
272                         server.sendBundle(server.latency, [15, group.nodeID] ++ args);
273                 };
274         }
276         setn { | ... args |
277                 nodeMap.set(*args);
278                 if(this.isPlaying) {
279                         server.sendBundle(server.latency, group.setnMsg(*args));
280                 };
281         }
283         setGroup { | args, useLatency = false |
284                 if(this.isPlaying) {
285                         server.sendBundle(if(useLatency) { server.latency }, [15, group.nodeID] ++ args);
286                 };
287         }
289         map { | ... args | // key(s), proxy, key(s), proxy ...
290                 var bundle;
291                 if(this.isPlaying) {
292                         bundle = List.new;
293                         nodeMap.unmapArgsToBundle(bundle, group.nodeID, args[0,2..args.size-2]);
294                         nodeMap.map(*args).updateBundle;
295                         nodeMap.addToBundle(bundle, group.nodeID);
296                         server.listSendBundle(server.latency, bundle);
297                 } { nodeMap.map(*args) }
298         }
300         mapn { | ... args |
301                 "NodeProxy: mapn is deprecated, please use map instead".postln;
302                 ^this.map(*args)
303         }
305         xset { | ... args |
306                 this.xFadePerform(\set, args)
307         }
308         xmap { | ... args |
309                 this.xFadePerform(\map, args)
310         }
311         xsetn { | ... args |
312                 this.xFadePerform(\setn, args)
313         }
314         xmapn { | ... args |
315                 "NodeProxy: xmapn is decrepated, please use xmap instead".postln;
316                 this.xFadePerform(\map, args)
317         }
318         xunset { | ... args |
319                 this.xFadePerform(\unset, args)
320         }
322         xFadePerform { | selector, args |
323                 var bundle;
324                 if(this.isPlaying)
325                 {
326                         nodeMap.performList(selector, args);
327                         this.sendEach(nil, true)
328                 } {
329                         this.performList(selector, args)
330                 }
331         }
333         mapEnvir { | ... keys | // map to current environment
334                 nodeMap.mapEnvir(*keys);
335                 if(this.isPlaying) {
336                         nodeMap.sendToNode(group);
337                 }
338         }
340         unset { | ... keys |
341                 var bundle = List.new;
342                 this.unsetToBundle(bundle, keys);
343                 if(bundle.notEmpty) {
344                         server.listSendBundle(server.latency, bundle)
345                 }
346         }
348         unmap { | ... keys |
349                 var bundle;
350                 if(keys.isEmpty) { keys = nodeMap.mappingKeys; if(keys.isEmpty) { ^this } };
351                 if(this.isPlaying) {
352                         bundle = List.new;
353                         nodeMap.unmapArgsToBundle(bundle, group.nodeID, keys);
354                         if(bundle.notEmpty) { server.listSendBundle(server.latency, bundle) };
355                 };
356                 nodeMap.unmap(*keys);
357         }
359         nodeMap_ { | map |
360                 this.setNodeMap(map, false)
361         }
363         setNodeMap { | map, xfade = true |
364                 var bundle, old, fadeTime;
365                 map.set(\fadeTime, this.fadeTime); // keep old fadeTime
366                 bundle = MixedBundle.new;
367                 old = nodeMap;
368                 nodeMap = map;
369                 this.linkNodeMap;
370                 if(this.isPlaying) {
371                         if(xfade) { this.sendEach(nil,true) }
372                         {
373                         this.unsetToBundle(bundle); // unmap old
374                         nodeMap.addToBundle(bundle, group); // map new
375                         bundle.schedSend(server, clock ? TempoClock.default, quant);
376                         }
377                 };
378         }
381         // play proxy as source of receiver
382         <-- { | proxy |
383                 var bundle = MixedBundle.new;
384                 this.source = proxy;
386                 if(proxy.monitorGroup.isPlaying) {
387                         proxy.stop(fadeTime: 0.5);
388                         if(this.monitorGroup.isPlaying.not) {
389                                 this.playToBundle(bundle, fadeTime:0.1)
390                         }
391                 };
392                 bundle.add(proxy.moveBeforeMsg(this));
393                 bundle.send(server, server.latency);
394         }
396         // map receiver to proxy input
397         // second argument is an adverb
398         <>> { | proxy, key = \in |
399                 proxy.perform('<<>', this, key);
400                 ^proxy
401         }
403         // map proxy to receiver input
404         // second argument is an adverb
405         <<> { | proxy, key = \in |
406                 var ctl, rate, numChannels, canBeMapped;
407                 if(proxy.isNil) { ^this.unmap(key) };
408                 ctl = this.controlNames.detect { |x| x.name == key };
409                 rate = ctl.rate ?? {
410                                 if(proxy.isNeutral) {
411                                         if(this.isNeutral) { \audio } { this.rate }
412                                 } {
413                                         proxy.rate
414                                 }
415                 };
416                 numChannels = ctl !? { ctl.defaultValue.asArray.size };         canBeMapped = proxy.initBus(rate, numChannels);
417                 if(canBeMapped) {
418                         if(this.isNeutral) { this.defineBus(rate, numChannels) };
419                         this.xmap(key, proxy);
420                 } {
421                         "Could not link node proxies, no matching input found.".warn
422                 };
423                 ^proxy // returns first argument for further chaining
424         }
430         // starting processes
432         spawn { | extraArgs, index = 0 |
433                         var bundle, obj, i;
434                         obj = objects.at(index);
435                         if(obj.notNil) {
436                                 i = this.index;
437                                 bundle = this.getBundle;
438                                 obj.spawnToBundle(bundle, extraArgs, this);
439                                 nodeMap.addToBundle(bundle, -1);
440                                 bundle.schedSend(server);
441                         }
442         }
445         send { | extraArgs, index, freeLast = true |
446                         var bundle, obj;
447                         if(objects.isEmpty) { ^this };
448                         if(index.isNil) {
449                                 bundle = this.getBundle;
450                                 if(freeLast) { this.stopAllToBundle(bundle) };
451                                 this.sendAllToBundle(bundle, extraArgs);
452                                 bundle.schedSend(server);
454                         } {
455                                 obj = objects.at(index);
456                                 if(obj.notNil) {
457                                         bundle = this.getBundle;
458                                         if(freeLast) { obj.stopToBundle(bundle) };
460                                         this.sendObjectToBundle(bundle, obj, extraArgs, index);
461                                         bundle.schedSend(server);
462                                 }
463                         }
464         }
466         sendAll { | extraArgs, freeLast = true |
467                 this.send(extraArgs, nil, freeLast);
468         }
470         sendEach { | extraArgs, freeLast = true |
471                         var bundle;
472                         bundle = this.getBundle;
473                         if(freeLast, { this.stopAllToBundle(bundle) });
474                         this.sendEachToBundle(bundle, extraArgs);
475                         bundle.schedSend(server);
477         }
479         quantize { | ... proxies |
480                 var quant = this.quant ? 1.0;
481                 ([this]++proxies).do { |x|
482                         x.quant = quant;
483                         x.send;
484                 }
485         }
487         wakeUp {        // do not touch internal state if already playing
488                 if(this.isPlaying.not) { this.deepWakeUp }
489         }
491         deepWakeUp {
492                         var bundle;
493                         bundle = MixedBundle.new;
494                         this.wakeUpToBundle(bundle);
495                         bundle.schedSend(server, clock ? TempoClock.default, quant)
496         }
502         // gui support
504         typeStr {
505                 if(this.rate === 'audio') { ^"ar" + this.numChannels };
506                 if(this.rate === 'control') { ^"kr" + this.numChannels };
507                 ^"ir";
508         }
510         edit { | nSliders, parent, bounds |
511                 ^NdefGui(this, nSliders ? this.getKeysValues.size.max(5), parent, bounds);
512         }
517         // interproxy structure and reading other busses
519         orderNodes { | ... proxies |
520                 var msg = this.moveBeforeMsg(*proxies);
521                 msg !? {
522                         server.sendBundle(nil, msg)
523                 }
524         }
526         getFamily { | set, alreadyAsked |
527                 var parents;
528                 parents = IdentitySet.new;
529                 alreadyAsked = alreadyAsked ?? { IdentitySet.new };
530                 if(alreadyAsked.includes(this).not) {
531                         alreadyAsked.add(this);
532                         objects.do { arg obj; parents.addAll(obj.parents) };
533                         parents.addAll(nodeMap.parents);
534                         parents.do { arg proxy; proxy.getFamily(parents, alreadyAsked) };
535                         set.add(this);
536                         set.addAll(parents);
537                 };
538                 ^set
539         }
541         getStructure { | alreadyAsked |
542                 var parents, substructure;
543                 parents = List.new;
544                 alreadyAsked = alreadyAsked ?? { IdentitySet.new };
545                 if(alreadyAsked.includes(this).not) {
546                         alreadyAsked.add(this);
547                         objects.do { arg obj; parents.addAll(obj.parents) };
548                         parents.addAll(nodeMap.parents);
549                         substructure = parents.collect { arg proxy; proxy.getStructure(alreadyAsked) };
550                         ^[this, substructure.flatten(1)];
551                 };
552                 ^nil
553         }
555         moveBeforeMsg { | ... proxies |
556                 var list;
557                 ([this] ++ proxies).do { |el|
558                         if(el.isPlaying) {
559                                 list = list.add(el.group);
560                                 if(el.monitor.isPlaying) {
561                                         list = list.add(el.monitor.group) // debatable. maybe check whether special
562                                 }
563                         }
564                 };
565                 ^list !? { Node.orderNodesMsg(list) }
566         }
573         // node map settings
575         internalKeys {
576                 ^#[\out, \i_out, \gate, \fadeTime];
577         }
579                 // return names in the order they have in .objects
580         controlNames { | except, addNodeMap = true |
581                 var all = Array.new; // Set doesn't work, because equality undefined for ControlName
582                 except = except ? this.internalKeys;
583                 objects.do { |el|
584                         el.controlNames.do { |item|
585                                 if(except.includes(item.name).not and: {
586                                         all.every { |cn| cn.name !== item.name }
587                                 }) {
588                                         all = all.add(item);
589                                 }
590                         };
591                 };
592                 ^if (addNodeMap.not or: nodeMap.isNil) { all } {
593                         this.addNodeMapControlNames(all, except)
594                 };
595         }
597                 // if a name is set in nodemap, overwrite the values in objCtlNames;
598                 // if a key is set in the nodemap, but is not used in the objects yet, add at the end.
599         addNodeMapControlNames { |objCtlNames, except = #[]|
600                 nodeMap.controlNames
601                         .reject { |ctlname| except.includes(ctlname.name) }
602                         .do { |mapCtl|
603                                 var index = objCtlNames.detectIndex { |objCtl| objCtl.name == mapCtl.name };
604                                 if (index.notNil) {
605                                         objCtlNames.put(index, mapCtl)
606                                 } {
607                                         objCtlNames = objCtlNames.add(mapCtl)
608                                 }
609                         };
610                 ^objCtlNames
611         }
613         resetNodeMap {
614                 this.nodeMap = ProxyNodeMap.new;
615         }
617         cleanNodeMap {
618                 var nodeMapSettingKeys, nodeMapMappingKeys, keysToUnset, keysToUnmap, currentKeys;
619                 if (nodeMap.isNil) { ^this };
621                 nodeMapSettingKeys = difference(nodeMap.settingKeys, this.internalKeys);
622                 nodeMapMappingKeys = difference(nodeMap.mappingKeys, this.internalKeys);
623                 currentKeys = this.controlNames(addNodeMap: false).collect(_.name);
624                 keysToUnset = difference(nodeMapSettingKeys, currentKeys);
625                 keysToUnmap = difference(nodeMapMappingKeys, currentKeys);
627                 keysToUnset.do(this.unset(_));
628                 keysToUnmap.do(this.unmap(_));
629         }
631         controlKeys { | except, noInternalKeys = true |
632                 var list = Array.new;
633                         if (noInternalKeys) { except = except ++ this.internalKeys; };
634                         this.controlNames.do { |el, i|
635                                 if(except.includes(el.name).not)
636                                 { list = list.add(el.name) }
637                         }
638                 ^list
639         }
641         getKeysValues { | keys, except, withDefaults = true, noInternalKeys = true |
642                 var pairs, result = [], myKeys, defaults, mapSettings;
643                 if (noInternalKeys) { except = except ++ this.internalKeys; };
645                 pairs = this.controlKeysValues(keys, except).clump(2);
646                 #myKeys, defaults = pairs.flop;
648                 mapSettings = nodeMap.settings;
649                 myKeys.collect { |key, i|
650                         var val, doAdd;
651                         val = mapSettings[key];
652                         doAdd = withDefaults or: val.notNil;
653                         if (doAdd) {
654                                 result = result.add([ key, (val ? defaults[i]).value ]);
655                         };
656                 }
657                 ^result
658         }
660         // controlPairs, gets default values
661         controlKeysValues { | keys, except |
662                 var list, fullList;
663                 except = except ? this.internalKeys;
664                 fullList = this.controlNames(except);
665                 if(keys.isNil or: { keys.isEmpty }) {
666                         list = Array(fullList.size * 2);
667                         fullList.do { |cn| list.add(cn.name).add(cn.defaultValue) }
668                 } {
669                         list = Array(keys.size * 2);
670                         keys.do { |key|
671                                 var val = fullList.detect { |cn| cn.name == key };
672                                 val = if(val.isNil) { 0 } { val.defaultValue };
673                                 list.add(key).add(val)
674                         }
675                 }
676                 ^list;
677         }
679         // derive names and default args from synthDefs
680         supplementNodeMap { | keys, replaceOldKeys=false |
681                 this.controlNames.do { |el|
682                                         var key;
683                                         key = el.name;
684                                         if (
685                                                 ( replaceOldKeys or: { nodeMap.at(key).isNil } )
686                                                 and:
687                                                 { keys.isNil or: { keys.includes(key) } }
688                                         ) { nodeMap.set(key, el.defaultValue) }
689                 }
690         }
696         // bundling messages
698         getBundle {
699                 var bundle;
700                 bundle =        MixedBundle.new;
701                 this.prepareToBundle(nil, bundle);
702                 ^bundle
703         }
705         prepareToBundle { | argGroup, bundle, addAction = \addToTail |
706                 if(this.isPlaying.not) {
707                                 group = Group.basicNew(server, this.defaultGroupID);
708                                 NodeWatcher.register(group);
709                                 group.isPlaying = server.serverRunning;
710                                 if(argGroup.isNil and: { parentGroup.isPlaying }) { argGroup = parentGroup };
711                                 bundle.addPrepare(group.newMsg(argGroup ?? { server.asGroup }, addAction));
712                 }
713         }
715         // bundle: apply the node map settings to the entire group
716         sendAllToBundle { | bundle, extraArgs |
717                                 objects.do { arg item;
718                                         item.playToBundle(bundle, extraArgs.value, this);
719                                 };
720                                 if(objects.notEmpty) { nodeMap.addToBundle(bundle, group) };
721         }
723         // bundle: apply the node map settings to each synth separately
724         sendEachToBundle { | bundle, extraArgs |
725                                 objects.do { arg item;
726                                         this.sendObjectToBundle(bundle, item, extraArgs.value)
727                                 }
728         }
730         // bundle: send single object
731         sendObjectToBundle { | bundle, object, extraArgs, index |
732                                 var synthID, target, nodes;
733                                 synthID = object.playToBundle(bundle, extraArgs.value, this);
734                                 if(synthID.notNil) {
735                                         if(index.notNil and: { objects.size > 1 }) { // if nil, all are sent anyway
736                                                 // make list of nodeIDs following the index
737                                                 nodes = Array(4);
738                                                 objects.doRange({ arg obj;
739                                                         var id = obj.nodeID;
740                                                         if(id.notNil and: { id != synthID })
741                                                                 { nodes = nodes ++ id ++ synthID };
742                                                 }, index + 1);
743                                                 if(nodes.size > 0) { bundle.add(["/n_before"] ++ nodes.reverse) };
744                                         };
745                                 nodeMap.addToBundle(bundle, synthID)
746                                 }
747         }
749         // bundle: remove single object
750         removeToBundle { | bundle, index, fadeTime |
751                 var obj, dt, playing;
752                 playing = this.isPlaying;
753                 obj = objects.removeAt(index);
755                 if(obj.notNil) {
756                                 dt = fadeTime ? this.fadeTime;
757                                 if(playing) { obj.stopToBundle(bundle, dt) };
758                                 obj.freeToBundle(bundle, dt);
759                 }
760         }
761         // bundle: remove all objects
762         removeAllToBundle { | bundle, fadeTime |
763                 var dt, playing;
764                 dt = fadeTime ? this.fadeTime;
765                 playing = this.isPlaying;
766                 objects.do { arg obj;
767                                 if(playing) { obj.stopToBundle(bundle, dt) };
768                                 obj.freeToBundle(bundle, dt);
769                 };
770                 objects.makeEmpty;
771         }
773         // bundle: stop single process
774         stopAllToBundle { | bundle, fadeTime |
775                 var obj, dt;
776                 dt = fadeTime ? this.fadeTime;
777                 if(this.isPlaying) {
778                         objects.do { |obj| obj.stopToBundle(bundle, dt) }
779                 }
780         }
783         loadToBundle { | bundle |
784                 this.reallocBusIfNeeded;
785                 objects.do { arg item, i;
786                         item.build(this, i);
787                         item.loadToBundle(bundle, server);
788                 };
789                 loaded = true;
790         }
792         unsetToBundle { | bundle, keys |
793                 var pairs = this.controlKeysValues(keys);
794                 if(this.isPlaying) {
795                         if(pairs.notEmpty) {
796                                 bundle.add([15, group.nodeID] ++ pairs);
797                         };
798                 };
799                 nodeMap.unset(*pairs[0,2..]);
800         }
802         wakeUpToBundle { | bundle, checkedAlready |
803                 if(checkedAlready.isNil) { checkedAlready = IdentitySet.new };
804                 if(checkedAlready.includes(this).not) {
805                         checkedAlready.add(this);
806                         this.wakeUpParentsToBundle(bundle, checkedAlready);
807                         if(loaded.not) { this.loadToBundle(bundle) };
808                         if(awake and: { this.isPlaying.not }) {
809                                 this.prepareToBundle(nil, bundle, \addToHead);
810                                 this.sendAllToBundle(bundle);
811                         };
812                 };
814         }
816         wakeUpParentsToBundle { | bundle, checkedAlready |
817                         nodeMap.wakeUpParentsToBundle(bundle, checkedAlready);
818                         objects.do { arg item; item.wakeUpParentsToBundle(bundle, checkedAlready) };
819         }
826         // allocation
828         defineBus { | rate = \audio, numChannels |
829                 super.defineBus(rate, numChannels);
830                 this.linkNodeMap;
831         }
833         reallocBusIfNeeded { // bus is reallocated only if the server was not booted on creation.
834                 if(busLoaded.not and: { bus.notNil }) {
835                         bus.realloc;
836                         this.linkNodeMap
837                 }
838         }
845         // network support
847         shouldAddObject { | obj | ^obj.readyForPlay } // shared node proxy overrides this
849         defaultGroupID { ^server.nextNodeID }
856         // private implementation
858         linkNodeMap {
859                 var index;
860                 index = this.index;
861                 if(index.notNil) { nodeMap.set(\out, index, \i_out, index) };
862                 nodeMap.proxy = this;
863         }
865         generateUniqueName {
866                         // if named, give us the name so we see it 
867                         // in synthdef names of the server's nodes. 
868                 var key = this.key ?? this.identityHash.abs;
869                 ^server.clientID.asString ++ key ++ "_";
870         }
872         prepareOutput {
873                 var parentPlaying;
874                 parentPlaying = this.addToChild;
875                 if(parentPlaying) { this.deepWakeUp };
876         }
878         addToChild {
879                 var child;
880                 child = buildProxyControl;
881                 if(child.notNil) { child.addParent(this) };
882                 ^child.isPlaying;
883         }
885                 // renames synthdef so one can use it in patterns
886         nameDef { |name, index = 0| 
887                 var func = objects[index].synthDef.func; 
888                 name = name ?? { 
889                         "New SynthDef name: ".post; 
890                         (this.key ++ "_" ++ index).asSymbol.postcs;
891                 };
892                 ^SynthDef(name, func); 
893         }
898 Ndef : NodeProxy {
900         classvar <>defaultServer, <>all;
901         var <>key;
903         *initClass { all = () }
905         *new { | key, object |
906                 // key may be simply a symbol, or an association of a symbol and a server name
907                 var res, server, dict;
909                 if(key.isKindOf(Association)) {
910                         server = Server.named.at(key.value);
911                         if(server.isNil) {
912                                 Error("Ndef(%): no server found with this name.".format(key)).throw
913                         };
914                         key = key.key;
915                 } {
916                         server = Server.default;
917                 };
919                 dict = this.dictFor(server);
920                 res = dict.envir.at(key);
921                 if(res.isNil) {
922                         res = super.new(server).key_(key);
923                         dict.envir.put(key, res)
924                 };
926                 object !? { res.source = object };
927                 ^res;
928         }
930         *ar { | key, numChannels, offset = 0 |
931                 ^this.new(key).ar(numChannels, offset)
932         }
934         *kr { | key, numChannels, offset = 0 |
935                 ^this.new(key).kr(numChannels, offset)
936         }
938         *clear { | fadeTime |
939                 all.do(_.clear(fadeTime));
940                 all.clear;
941         }
943         *dictFor { | server |
944                 var dict = all.at(server.name);
945                 if(dict.isNil) {
946                         dict = ProxySpace.new(server); // use a proxyspace for ease of access.
947                         all.put(server.name, dict);
948                         dict.registerServer;
949                 };
950                 ^dict
951         }
953         proxyspace {
954                 ^this.class.dictFor(this.server)
955         }
957         storeOn { | stream |
958                 this.printOn(stream);
959         }
960         printOn { | stream |
961                 var serverString = if (server == Server.default) { "" } {
962                         " ->" + server.name.asCompileString;
963                 };
964                 stream << this.class.name << "(" <<< this.key << serverString << ")"
965         }