3 var <group, <objects, <nodeMap;
4 var <loaded=false, <>awake=true, <paused=false;
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) };
17 nodeMap = ProxyNodeMap.new;
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
27 this.freeBus; // free the bus from the server allocator
28 this.init; // reset the environment
31 end { | fadeTime, reset = false |
32 var dt = fadeTime ? this.fadeTime;
35 (dt + (server.latency ? 0)).wait;
40 isPlaying { ^group.isPlaying }
42 free { | fadeTime, freeGroup = true |
45 bundle = MixedBundle.new;
46 if(fadeTime.notNil) { bundle.add([15, group.nodeID, "fadeTime", fadeTime]) };
47 this.stopAllToBundle(bundle, fadeTime);
49 bundle.sched((fadeTime ? this.fadeTime) + (server.latency ? 0), { group.free });
55 release { | fadeTime |
56 this.free(fadeTime, false)
60 if(this.isPlaying) { objects.do { |item| item.pause(clock, quant) } };
66 if(this.isPlaying) { objects.do { |item| item.resume(clock, quant) } };
70 if(dur.isNil) { this.unset(\fadeTime) } { this.set(\fadeTime, dur) };
74 ^nodeMap.at(\fadeTime).value ? 0.02;
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 };
86 if(group.isPlaying) { group.moveToHead(parentGroup) }
99 this.put(nil, obj, 0, nil, false);
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)
114 ^objects.at(index).source
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)
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;
129 { this.removeAllToBundle(bundle) }
130 { this.removeToBundle(bundle, index) };
131 objects = objects.put(orderIndex, container);
133 format("failed to add % to node proxy: %", obj, this).inform;
137 if(server.serverRunning) {
140 this.prepareToBundle(nil, bundle);
142 container.loadToBundle(bundle, server);
145 container.wakeUpParentsToBundle(bundle);
146 this.sendObjectToBundle(bundle, container, extraArgs, index);
148 nodeMap.wakeUpParentsToBundle(bundle);
149 bundle.schedSend(server, clock ? TempoClock.default, quant);
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))
163 list.do { |item, i| this.put(i + index, item, channelOffset.wrapAt(i)) } }
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))
172 this.put(i, \filter -> func)
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;
181 { this.removeAllToBundle(bundle, fadeTime) }
182 { this.removeToBundle(bundle, index, fadeTime) };
183 bundle.schedSend(server);
189 bundle = MixedBundle.new;
190 this.stopAllToBundle(bundle);
191 bundle.schedSend(server, clock ? TempoClock.default, quant);
192 bundle = MixedBundle.new;
194 this.loadToBundle(bundle);
195 this.sendAllToBundle(bundle);
196 bundle.schedSend(server, clock ? TempoClock.default, quant);
204 nodeMap.setRates(args);
208 setRates { | ... args |
209 nodeMap.setRates(args);
213 server_ { | inServer |
214 if(this.isNeutral.not) {
222 if(server != inBus.server) { Error("can't change the server").throw };
230 if(inGroup.server !== server, { Error("cannot move to another server").throw });
231 NodeWatcher.register(inGroup.isPlaying_(true)); // assume it is playing
233 { bundle = MixedBundle.new;
234 this.stopAllToBundle(bundle);
236 this.sendAllToBundle(bundle);
237 bundle.schedSend(server, clock ? TempoClock.default, 0.0);
238 } { group = inGroup };
243 proxies = proxies.asCollection;
244 proxies.do { arg item; item.wakeUp };
245 this.readFromBus(proxies)
248 readFromBus { | busses |
250 busses = busses.asCollection;
251 n = this.numChannels;
252 busses.do { arg item, i;
253 x = min(item.numChannels ? n, n);
255 SynthControl.new("system_link_" ++ this.rate ++ "_" ++ x),
257 ["in", item.index, "out", this.index]
267 // modifying context, setting controls
269 set { | ... args | // pairs of keys or indices and value
272 server.sendBundle(server.latency, [15, group.nodeID] ++ args);
279 server.sendBundle(server.latency, group.setnMsg(*args));
283 setGroup { | args, useLatency = false |
285 server.sendBundle(if(useLatency) { server.latency }, [15, group.nodeID] ++ args);
289 map { | ... args | // key(s), proxy, key(s), proxy ...
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) }
301 "NodeProxy: mapn is deprecated, please use map instead".postln;
306 this.xFadePerform(\set, args)
309 this.xFadePerform(\map, args)
312 this.xFadePerform(\setn, args)
315 "NodeProxy: xmapn is decrepated, please use xmap instead".postln;
316 this.xFadePerform(\map, args)
318 xunset { | ... args |
319 this.xFadePerform(\unset, args)
322 xFadePerform { | selector, args |
326 nodeMap.performList(selector, args);
327 this.sendEach(nil, true)
329 this.performList(selector, args)
333 mapEnvir { | ... keys | // map to current environment
334 nodeMap.mapEnvir(*keys);
336 nodeMap.sendToNode(group);
341 var bundle = List.new;
342 this.unsetToBundle(bundle, keys);
343 if(bundle.notEmpty) {
344 server.listSendBundle(server.latency, bundle)
350 if(keys.isEmpty) { keys = nodeMap.mappingKeys; if(keys.isEmpty) { ^this } };
353 nodeMap.unmapArgsToBundle(bundle, group.nodeID, keys);
354 if(bundle.notEmpty) { server.listSendBundle(server.latency, bundle) };
356 nodeMap.unmap(*keys);
360 this.setNodeMap(map, false)
363 setNodeMap { | map, xfade = true |
364 var bundle, old, fadeTime;
365 map.set(\fadeTime, this.fadeTime); // keep old fadeTime
366 bundle = MixedBundle.new;
371 if(xfade) { this.sendEach(nil,true) }
373 this.unsetToBundle(bundle); // unmap old
374 nodeMap.addToBundle(bundle, group); // map new
375 bundle.schedSend(server, clock ? TempoClock.default, quant);
381 // play proxy as source of receiver
383 var bundle = MixedBundle.new;
386 if(proxy.monitorGroup.isPlaying) {
387 proxy.stop(fadeTime: 0.5);
388 if(this.monitorGroup.isPlaying.not) {
389 this.playToBundle(bundle, fadeTime:0.1)
392 bundle.add(proxy.moveBeforeMsg(this));
393 bundle.send(server, server.latency);
396 // map receiver to proxy input
397 // second argument is an adverb
398 <>> { | proxy, key = \in |
399 proxy.perform('<<>', this, key);
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 };
410 if(proxy.isNeutral) {
411 if(this.isNeutral) { \audio } { this.rate }
416 numChannels = ctl !? { ctl.defaultValue.asArray.size }; canBeMapped = proxy.initBus(rate, numChannels);
418 if(this.isNeutral) { this.defineBus(rate, numChannels) };
419 this.xmap(key, proxy);
421 "Could not link node proxies, no matching input found.".warn
423 ^proxy // returns first argument for further chaining
430 // starting processes
432 spawn { | extraArgs, index = 0 |
434 obj = objects.at(index);
437 bundle = this.getBundle;
438 obj.spawnToBundle(bundle, extraArgs, this);
439 nodeMap.addToBundle(bundle, -1);
440 bundle.schedSend(server);
445 send { | extraArgs, index, freeLast = true |
447 if(objects.isEmpty) { ^this };
449 bundle = this.getBundle;
450 if(freeLast) { this.stopAllToBundle(bundle) };
451 this.sendAllToBundle(bundle, extraArgs);
452 bundle.schedSend(server);
455 obj = objects.at(index);
457 bundle = this.getBundle;
458 if(freeLast) { obj.stopToBundle(bundle) };
460 this.sendObjectToBundle(bundle, obj, extraArgs, index);
461 bundle.schedSend(server);
466 sendAll { | extraArgs, freeLast = true |
467 this.send(extraArgs, nil, freeLast);
470 sendEach { | extraArgs, freeLast = true |
472 bundle = this.getBundle;
473 if(freeLast, { this.stopAllToBundle(bundle) });
474 this.sendEachToBundle(bundle, extraArgs);
475 bundle.schedSend(server);
479 quantize { | ... proxies |
480 var quant = this.quant ? 1.0;
481 ([this]++proxies).do { |x|
487 wakeUp { // do not touch internal state if already playing
488 if(this.isPlaying.not) { this.deepWakeUp }
493 bundle = MixedBundle.new;
494 this.wakeUpToBundle(bundle);
495 bundle.schedSend(server, clock ? TempoClock.default, quant)
505 if(this.rate === 'audio') { ^"ar" + this.numChannels };
506 if(this.rate === 'control') { ^"kr" + this.numChannels };
510 edit { | nSliders, parent, bounds |
511 ^NdefGui(this, nSliders ? this.getKeysValues.size.max(5), parent, bounds);
517 // interproxy structure and reading other busses
519 orderNodes { | ... proxies |
520 var msg = this.moveBeforeMsg(*proxies);
522 server.sendBundle(nil, msg)
526 getFamily { | set, alreadyAsked |
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) };
541 getStructure { | alreadyAsked |
542 var parents, substructure;
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)];
555 moveBeforeMsg { | ... proxies |
557 ([this] ++ proxies).do { |el|
559 list = list.add(el.group);
560 if(el.monitor.isPlaying) {
561 list = list.add(el.monitor.group) // debatable. maybe check whether special
565 ^list !? { Node.orderNodesMsg(list) }
576 ^#[\out, \i_out, \gate, \fadeTime];
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;
584 el.controlNames.do { |item|
585 if(except.includes(item.name).not and: {
586 all.every { |cn| cn.name !== item.name }
592 ^if (addNodeMap.not or: nodeMap.isNil) { all } {
593 this.addNodeMapControlNames(all, except)
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 = #[]|
601 .reject { |ctlname| except.includes(ctlname.name) }
603 var index = objCtlNames.detectIndex { |objCtl| objCtl.name == mapCtl.name };
605 objCtlNames.put(index, mapCtl)
607 objCtlNames = objCtlNames.add(mapCtl)
614 this.nodeMap = ProxyNodeMap.new;
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(_));
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) }
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|
651 val = mapSettings[key];
652 doAdd = withDefaults or: val.notNil;
654 result = result.add([ key, (val ? defaults[i]).value ]);
660 // controlPairs, gets default values
661 controlKeysValues { | keys, except |
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) }
669 list = Array(keys.size * 2);
671 var val = fullList.detect { |cn| cn.name == key };
672 val = if(val.isNil) { 0 } { val.defaultValue };
673 list.add(key).add(val)
679 // derive names and default args from synthDefs
680 supplementNodeMap { | keys, replaceOldKeys=false |
681 this.controlNames.do { |el|
685 ( replaceOldKeys or: { nodeMap.at(key).isNil } )
687 { keys.isNil or: { keys.includes(key) } }
688 ) { nodeMap.set(key, el.defaultValue) }
700 bundle = MixedBundle.new;
701 this.prepareToBundle(nil, bundle);
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));
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);
720 if(objects.notEmpty) { nodeMap.addToBundle(bundle, group) };
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)
730 // bundle: send single object
731 sendObjectToBundle { | bundle, object, extraArgs, index |
732 var synthID, target, nodes;
733 synthID = object.playToBundle(bundle, extraArgs.value, this);
735 if(index.notNil and: { objects.size > 1 }) { // if nil, all are sent anyway
736 // make list of nodeIDs following the index
738 objects.doRange({ arg obj;
740 if(id.notNil and: { id != synthID })
741 { nodes = nodes ++ id ++ synthID };
743 if(nodes.size > 0) { bundle.add(["/n_before"] ++ nodes.reverse) };
745 nodeMap.addToBundle(bundle, synthID)
749 // bundle: remove single object
750 removeToBundle { | bundle, index, fadeTime |
751 var obj, dt, playing;
752 playing = this.isPlaying;
753 obj = objects.removeAt(index);
756 dt = fadeTime ? this.fadeTime;
757 if(playing) { obj.stopToBundle(bundle, dt) };
758 obj.freeToBundle(bundle, dt);
761 // bundle: remove all objects
762 removeAllToBundle { | bundle, fadeTime |
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);
773 // bundle: stop single process
774 stopAllToBundle { | bundle, fadeTime |
776 dt = fadeTime ? this.fadeTime;
778 objects.do { |obj| obj.stopToBundle(bundle, dt) }
783 loadToBundle { | bundle |
784 this.reallocBusIfNeeded;
785 objects.do { arg item, i;
787 item.loadToBundle(bundle, server);
792 unsetToBundle { | bundle, keys |
793 var pairs = this.controlKeysValues(keys);
796 bundle.add([15, group.nodeID] ++ pairs);
799 nodeMap.unset(*pairs[0,2..]);
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);
816 wakeUpParentsToBundle { | bundle, checkedAlready |
817 nodeMap.wakeUpParentsToBundle(bundle, checkedAlready);
818 objects.do { arg item; item.wakeUpParentsToBundle(bundle, checkedAlready) };
828 defineBus { | rate = \audio, numChannels |
829 super.defineBus(rate, numChannels);
833 reallocBusIfNeeded { // bus is reallocated only if the server was not booted on creation.
834 if(busLoaded.not and: { bus.notNil }) {
847 shouldAddObject { | obj | ^obj.readyForPlay } // shared node proxy overrides this
849 defaultGroupID { ^server.nextNodeID }
856 // private implementation
861 if(index.notNil) { nodeMap.set(\out, index, \i_out, index) };
862 nodeMap.proxy = this;
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 ++ "_";
874 parentPlaying = this.addToChild;
875 if(parentPlaying) { this.deepWakeUp };
880 child = buildProxyControl;
881 if(child.notNil) { child.addParent(this) };
885 // renames synthdef so one can use it in patterns
886 nameDef { |name, index = 0|
887 var func = objects[index].synthDef.func;
889 "New SynthDef name: ".post;
890 (this.key ++ "_" ++ index).asSymbol.postcs;
892 ^SynthDef(name, func);
900 classvar <>defaultServer, <>all;
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);
912 Error("Ndef(%): no server found with this name.".format(key)).throw
916 server = Server.default;
919 dict = this.dictFor(server);
920 res = dict.envir.at(key);
922 res = super.new(server).key_(key);
923 dict.envir.put(key, res)
926 object !? { res.source = object };
930 *ar { | key, numChannels, offset = 0 |
931 ^this.new(key).ar(numChannels, offset)
934 *kr { | key, numChannels, offset = 0 |
935 ^this.new(key).kr(numChannels, offset)
938 *clear { | fadeTime |
939 all.do(_.clear(fadeTime));
943 *dictFor { | server |
944 var dict = all.at(server.name);
946 dict = ProxySpace.new(server); // use a proxyspace for ease of access.
947 all.put(server.name, dict);
954 ^this.class.dictFor(this.server)
958 this.printOn(stream);
961 var serverString = if (server == Server.default) { "" } {
962 " ->" + server.name.asCompileString;
964 stream << this.class.name << "(" <<< this.key << serverString << ")"