3 var <>numPrivateAudioBusChannels=112;
4 var <>numControlBusChannels=4096;
5 var <>numInputBusChannels=8;
6 var <>numOutputBusChannels=8;
10 var <>maxSynthDefs=1024;
11 var <>protocol = \udp;
13 var <>hardwareBufferSize = nil;
17 var <>numWireBufs = 64;
19 var <>sampleRate = nil;
20 var <>loadDefs = true;
22 var <>inputStreamsEnabled;
23 var <>outputStreamsEnabled;
26 var <>outDevice = nil;
29 var <>zeroConf = false; // Whether server publishes port to Bonjour, etc.
31 var <>restrictedPath = nil;
33 var <>initialNodeID = 1000;
34 var <>remoteControlVolume = false;
36 var <>memoryLocking = false;
37 var <>threads = nil; // for supernova
40 ^if(inDevice == outDevice)
51 inDevice = outDevice = dev;
57 // prevent buffer conflicts in Server-prepareForRecord and Server-scope by
58 // ensuring reserved buffers
64 numBuffers_ { | argNumBuffers |
65 numBuffers = argNumBuffers + 2
68 asOptionsString { | port = 57110 |
70 o = if (protocol == \tcp, " -t ", " -u ");
73 o = o ++ " -a " ++ (numPrivateAudioBusChannels + numInputBusChannels + numOutputBusChannels) ;
75 if (numControlBusChannels != 4096, {
76 o = o ++ " -c " ++ numControlBusChannels;
78 if (numInputBusChannels != 8, {
79 o = o ++ " -i " ++ numInputBusChannels;
81 if (numOutputBusChannels != 8, {
82 o = o ++ " -o " ++ numOutputBusChannels;
84 if (numBuffers != 1024, {
85 o = o ++ " -b " ++ numBuffers;
87 if (maxNodes != 1024, {
88 o = o ++ " -n " ++ maxNodes;
90 if (maxSynthDefs != 1024, {
91 o = o ++ " -d " ++ maxSynthDefs;
93 if (blockSize != 64, {
94 o = o ++ " -z " ++ blockSize;
96 if (hardwareBufferSize.notNil, {
97 o = o ++ " -Z " ++ hardwareBufferSize;
99 if (memSize != 8192, {
100 o = o ++ " -m " ++ memSize;
102 if (numRGens != 64, {
103 o = o ++ " -r " ++ numRGens;
105 if (numWireBufs != 64, {
106 o = o ++ " -w " ++ numWireBufs;
108 if (sampleRate.notNil, {
109 o = o ++ " -S " ++ sampleRate;
114 if (inputStreamsEnabled.notNil, {
115 o = o ++ " -I " ++ inputStreamsEnabled ;
117 if (outputStreamsEnabled.notNil, {
118 o = o ++ " -O " ++ outputStreamsEnabled ;
120 if ((thisProcess.platform.name!=\osx) or: {inDevice == outDevice})
124 o = o ++ " -H %".format(inDevice.quote);
128 o = o ++ " -H % %".format(inDevice.asString.quote, outDevice.asString.quote);
130 if (verbosity != 0, {
131 o = o ++ " -v " ++ verbosity;
136 if (restrictedPath.notNil, {
137 o = o ++ " -P " ++ restrictedPath;
142 if (threads.notNil, {
143 if (Server.program.asString.endsWith("supernova")) {
144 o = o ++ " -T " ++ threads;
150 firstPrivateBus { // after the outs and ins
151 ^numOutputBusChannels + numInputBusChannels
155 ^numPrivateAudioBusChannels + numInputBusChannels + numOutputBusChannels
160 ^this.primitiveFailed
166 ^this.primitiveFailed
170 ^this.prListDevices(1, 1);
174 ^this.prListDevices(1, 0);
178 ^this.prListDevices(0, 1);
187 ^super.new.connect(port)
191 // never ever copy! will cause duplicate calls to the finalizer!
196 _ServerShmInterface_connectSharedMem
197 ^this.primitiveFailed
201 _ServerShmInterface_disconnectSharedMem
202 ^this.primitiveFailed
206 _ServerShmInterface_getControlBusValue
207 ^this.primitiveFailed
210 getControlBusValues {
211 _ServerShmInterface_getControlBusValues
212 ^this.primitiveFailed
216 _ServerShmInterface_setControlBusValue
217 ^this.primitiveFailed
220 setControlBusValues {
221 _ServerShmInterface_setControlBusValues
222 ^this.primitiveFailed
227 classvar <>local, <>internal, <default, <>named, <>set, <>program, <>sync_s = true;
229 var <name, <>addr, <clientID=0;
230 var <isLocal, <inProcess, <>sendQuit, <>remoteControlled;
231 var <serverRunning = false, <serverBooting = false, bootNotifyFirst = false;
232 var <>options, <>latency = 0.2, <dumpMode = 0, <notify = true, <notified=false;
234 var <controlBusAllocator;
235 var <audioBusAllocator;
236 var <bufferAllocator;
237 var <scopeBufferAllocator;
238 var <syncThread, <syncTasks;
240 var <numUGens=0, <numSynths=0, <numGroups=0, <numSynthDefs=0;
241 var <avgCPU, <peakCPU;
242 var <sampleRate, <actualSampleRate;
244 var alive = false, booting = false, aliveThread, <>aliveThreadPeriod = 0.7, statusWatcher;
247 var <window, <>scopeWindow;
249 var recordBuf, <recordNode, <>recHeaderFormat="aiff", <>recSampleFormat="float";
258 default = server; // sync with s?
259 if (sync_s, { thisProcess.interpreter.s = server });
260 this.all.do(_.changed(\default));
263 *new { arg name, addr, options, clientID=0;
264 ^super.new.init(name, addr, options, clientID)
268 *all_ { arg dict; set = dict }
270 init { arg argName, argAddr, argOptions, argClientID;
271 name = argName.asSymbol;
273 clientID = argClientID;
274 options = argOptions ? ServerOptions.new;
275 if (addr.isNil, { addr = NetAddr("127.0.0.1", 57110) });
276 inProcess = addr.addr == 0;
277 isLocal = inProcess || { addr.addr == 2130706433 };
278 remoteControlled = isLocal;
279 serverRunning = false;
280 named.put(name, this);
283 Server.changed(\serverAdded, this);
284 volume = Volume.new(server: this, persist: true);
288 nodeAllocator = NodeIDAllocator(clientID, options.initialNodeID);
289 this.sendMsg("/g_new", 1, 0, 0);
291 ServerTree.run(this);
294 this.newNodeAllocators;
295 this.newBusAllocators;
296 this.newBufferAllocators;
297 this.newScopeBufferAllocators;
298 NotificationCenter.notify(this, \newAllocators);
302 nodeAllocator = NodeIDAllocator(clientID, options.initialNodeID);
306 controlBusAllocator = ContiguousBlockAllocator.new(options.numControlBusChannels);
307 audioBusAllocator = ContiguousBlockAllocator.new(options.numAudioBusChannels,
308 options.firstPrivateBus);
311 newBufferAllocators {
312 bufferAllocator = ContiguousBlockAllocator.new(options.numBuffers);
315 newScopeBufferAllocators {
317 scopeBufferAllocator = StackNumberAllocator.new(0, 127);
325 ^nodeAllocator.allocPerm
327 freePermNodeID { |id|
328 ^nodeAllocator.freePerm(id)
332 Class.initClassTree(ServerOptions);
333 Class.initClassTree(NotificationCenter);
334 named = IdentityDictionary.new;
336 default = local = Server.new(\localhost, NetAddr("127.0.0.1", 57110));
337 internal = Server.new(\internal, NetAddr.new);
340 *fromName { arg name;
341 ^Server.named[name] ?? {
342 Server(name, NetAddr.new("127.0.0.1", 57110), ServerOptions.new, 0)
346 // bundling support added
347 sendMsg { arg ... msg;
350 sendBundle { arg time ... msgs;
351 addr.sendBundle(time, *msgs)
354 sendRaw { arg rawArray;
355 addr.sendRaw(rawArray);
358 sendMsgSync { arg condition ... args;
360 if (condition.isNil) { condition = Condition.new };
361 cmdName = args[0].asString;
362 if (cmdName[0] != $/) { cmdName = cmdName.insert(0, $/) };
363 resp = OSCFunc({|msg|
364 if (msg[1].asString == cmdName) {
366 condition.test = true;
370 condition.test = false;
371 addr.sendBundle(nil, args);
375 sync { arg condition, bundles, latency; // array of bundles that cause async action
376 addr.sync(condition, bundles, latency)
379 schedSync { arg func;
380 syncTasks = syncTasks.add(func);
381 if(syncThread.isNil) {
382 syncThread = Routine.run {
383 var c; c = Condition.new;
384 while { syncTasks.notEmpty } { syncTasks.removeAt(0).value(c) };
391 // bundling support added
392 listSendMsg { arg msg;
395 listSendBundle { arg time, msgs;
396 addr.sendBundle(time, *(msgs.asArray));
399 // load from disk locally, send remote
400 sendSynthDef { arg name, dir;
402 dir = dir ? SynthDef.synthDefDir;
403 file = File(dir ++ name ++ ".scsyndef","r");
404 if (file.isNil, { ^nil });
406 buffer = Int8Array.newClear(file.length);
411 this.sendMsg("/d_recv", buffer);
413 // tell server to load from disk
414 loadSynthDef { arg name, completionMsg, dir;
415 dir = dir ? SynthDef.synthDefDir;
417 ["/d_load", dir ++ name ++ ".scsyndef", completionMsg ]
421 loadDirectory { arg dir, completionMsg;
422 this.listSendMsg(["/d_loadDir", dir, completionMsg]);
425 serverRunning_ { arg val;
427 { this.changed(\bundling); }.defer;
429 if (val != serverRunning) {
430 if(thisProcess.platform.isSleeping.not) {
432 if (serverRunning.not) {
434 ServerQuit.run(this);
436 if (serverInterface.notNil) {
437 serverInterface.disconnect;
438 serverInterface = nil;
441 AppClock.sched(5.0, {
442 // still down after 5 seconds, assume server is really dead
443 // if you explicitly shut down the server then newAllocators
444 // and the \newAllocators notification will happen immediately
445 if(serverRunning.not) {
446 NotificationCenter.notify(this,\didQuit);
449 if(this.isLocal.not){
455 ServerBoot.run(this);
457 { this.changed(\serverRunning); }.defer;
463 wait { arg responseName;
465 routine = thisThread;
467 routine.resume(true);
468 }, responseName, addr).oneShot;
471 waitForBoot { arg onComplete, limit=100, onFailure;
472 // onFailure.true: why is this necessary?
473 // this.boot also calls doWhenBooted.
474 // doWhenBooted prints the normal boot failure message.
475 // if the server fails to boot, the failure error gets posted TWICE.
476 // So, we suppress one of them.
477 if(serverRunning.not) { this.boot(onFailure: true) };
478 this.doWhenBooted(onComplete, limit, onFailure);
481 doWhenBooted { arg onComplete, limit=100, onFailure;
482 var mBootNotifyFirst = bootNotifyFirst, postError = true;
483 bootNotifyFirst = false;
488 or: (serverBooting and: mBootNotifyFirst.not))
489 and: {(limit = limit - 1) > 0})
490 and: { pid.tryPerform(\pidRunning) == true }
495 if(serverRunning.not,{
496 if(onFailure.notNil) {
497 postError = (onFailure.value == false);
500 "server failed to start".error;
501 "For advice: [http://supercollider.sf.net/wiki/index.php/ERROR:_server_failed_to_start]".postln;
503 serverBooting = false;
504 this.changed(\serverRunning);
509 bootSync { arg condition;
510 condition ?? { condition = Condition.new };
511 condition.test = false;
513 // Setting func to true indicates that our condition has become true and we can go when signaled.
514 condition.test = true;
520 ping { arg n=1, wait=0.1, func;
521 var result=0, pingFunc;
522 if(serverRunning.not) { "server not running".postln; ^this };
526 t = Main.elapsedTime;
528 dt = Main.elapsedTime - t;
529 ("measured latency:" + dt + "s").postln;
530 result = max(result, dt);
533 SystemClock.sched(wait, {pingFunc.value; nil })
535 ("maximum determined latency of" + name + ":" + result + "s").postln;
544 if(statusWatcher.isNil) {
550 this.sendNotifyRequest;
551 "Receiving notification messages from server %\n".postf(this.name);
555 #cmd, one, numUGens, numSynths, numGroups, numSynthDefs,
556 avgCPU, peakCPU, sampleRate, actualSampleRate = msg;
558 this.serverRunning_(true);
559 this.changed(\counts);
562 }, '/status.reply', addr).fix;
564 statusWatcher.enable;
568 cachedBuffersDo { |func| Buffer.cachedBuffersDo(this, func) }
569 cachedBufferAt { |bufnum| ^Buffer.cachedBufferAt(this, bufnum) }
572 ^Bus(\audio, this.options.numOutputBusChannels, this.options.numInputBusChannels, this);
576 ^Bus(\audio, 0, this.options.numOutputBusChannels, this);
579 startAliveThread { arg delay=0.0;
580 this.addStatusWatcher;
582 aliveThread = Routine({
583 // this thread polls the server to see if it is alive
587 aliveThreadPeriod.wait;
588 this.serverRunning = alive;
592 AppClock.play(aliveThread);
597 if( aliveThread.notNil, {
601 if( statusWatcher.notNil, {
606 aliveThreadIsRunning {
607 ^aliveThread.notNil and: {aliveThread.isPlaying}
611 server.stopAliveThread;
612 server.startAliveThread(server.aliveThreadPeriod);
616 boot { arg startAliveThread=true, recover=false, onFailure;
618 if (serverRunning, { "server already running".inform; ^this });
619 if (serverBooting, { "server already booting".inform; ^this });
621 serverBooting = true;
622 if(startAliveThread, { this.startAliveThread });
623 if(recover) { this.newNodeAllocators } { this.newAllocators };
624 bootNotifyFirst = true;
626 serverBooting = false;
627 if (sendQuit.isNil) {
628 sendQuit = this.inProcess or: {this.isLocal};
631 if (this.inProcess) {
632 serverInterface = ServerShmInterface(thisProcess.pid);
635 serverInterface = ServerShmInterface(addr.port);
640 if(volume.volume != 0.0) {
643 }, onFailure: onFailure ? false);
644 if (remoteControlled.not, {
645 "You will have to manually boot remote server.".inform;
653 "booting internal".inform;
656 //this.serverRunning = true;
657 pid = thisProcess.pid;
659 if (serverInterface.notNil) {
660 serverInterface.disconnect;
661 serverInterface = nil;
664 pid = (program ++ options.asOptionsString(addr.port)).unixCmd;
665 //unixCmd(program ++ options.asOptionsString(addr.port)).postln;
666 ("booting " ++ addr.port.asString).inform;
670 reboot { arg func; // func is evaluated when server is off
671 if (isLocal.not) { "can't reboot a remote server".inform; ^this };
690 notify_ { |flag = true|
694 this.sendNotifyRequest(true);
696 "Receiving notification messages from server %\n".postf(this.name);
699 this.sendNotifyRequest(false);
701 "Switched off notification messages from server %\n".postf(this.name);
705 sendNotifyRequest { arg flag=true;
707 addr.sendMsg("/notify", flag.binaryValue);
710 dumpOSC { arg code=1;
712 0 - turn dumping OFF.
713 1 - print the parsed contents of the message.
714 2 - print the contents in hexadecimal.
715 3 - print both the parsed and hexadecimal representations of the contents.
718 this.sendMsg(\dumpOSC,code);
722 var serverReallyQuitWatcher, serverReallyQuit = false;
724 statusWatcher.disable;
726 serverReallyQuitWatcher = OSCFunc({ |msg|
727 if(msg[1] == '/quit') {
728 statusWatcher.enable;
729 serverReallyQuit = true;
730 serverReallyQuitWatcher.free;
733 // don't accumulate quit-watchers if /done doesn't come back
734 AppClock.sched(3.0, {
735 if(serverReallyQuit.not) {
736 "Server % failed to quit after 3.0 seconds.".format(this.name).warn;
737 serverReallyQuitWatcher.free;
742 addr.sendMsg("/quit");
745 "quit done\n".inform;
747 "/quit sent\n".inform;
753 serverBooting = false;
755 this.serverRunning = false;
756 if(scopeWindow.notNil) { scopeWindow.quit };
757 RootNode(this).freeAll;
758 if(volume.isPlaying) {
766 if (server.sendQuit === true) {
772 // if you see Exception in World_OpenUDP: unable to bind udp socket
773 // its because you have multiple servers running, left
774 // over from crashes, unexpected quits etc.
775 // you can't cause them to quit via OSC (the boot button)
777 // this brutally kills them all off
778 thisProcess.platform.killAll(this.program.basename);
782 this.sendMsg("/g_freeAll", 0);
783 this.sendMsg("/clearSched");
787 *freeAll { arg evenRemote = false;
790 if ( server.serverRunning ) { server.freeAll }
794 if (server.isLocal and:{ server.serverRunning }) { server.freeAll }
799 *hardFreeAll { arg evenRemote = false;
806 if (server.isLocal) { server.freeAll }
812 ^this.all.select(_.serverRunning)
818 openBundle { arg bundle; // pass in a bundle that you want to
819 // continue adding to, or nil for a new bundle.
821 bundle = addr.bundle.addAll(bundle);
822 addr.bundle = []; // debatable
824 addr = BundleNetAddr.copyFrom(addr, bundle);
826 closeBundle { arg time; // set time to false if you don't want to send.
829 bundle = addr.closeBundle(time);
830 addr = addr.saveAddr;
832 "there is no open bundle.".warn
836 makeBundle { arg time, func, bundle;
837 this.openBundle(bundle);
840 bundle = this.closeBundle(time);
842 addr = addr.saveAddr; // on error restore the normal NetAddr
848 ^this.makeBundle(this.latency, func)
851 // internal server commands
853 ^options.bootInProcess;
857 ^this.primitiveFailed
859 allocSharedControls { arg numControls=1024;
861 ^this.primitiveFailed
863 setSharedControl { arg num, value;
865 ^this.primitiveFailed
867 getSharedControl { arg num;
869 ^this.primitiveFailed
875 this.prepareForRecord(path);
881 if(recordNode.isNil){
882 recordNode = Synth.tail(RootNode(this), "server-record",
883 [\bufnum, recordBuf.bufnum]);
886 if (recordBuf.notNil) { recordBuf.close {|buf| buf.freeMsg }; recordBuf = nil; };
891 "Recording: %\n".postf(recordBuf.path);
896 recordNode.notNil.if({ recordNode.run(false); "Paused".postln }, { "Not Recording".warn });
900 if(recordNode.notNil) {
903 recordBuf.close({ |buf| buf.freeMsg });
904 "Recording Stopped: %\n".postf(recordBuf.path);
911 prepareForRecord { arg path;
913 if(File.exists(thisProcess.platform.recordingsDir).not) {
914 thisProcess.platform.recordingsDir.mkdir
917 // temporary kludge to fix Date's brokenness on windows
918 if(thisProcess.platform.name == \windows) {
919 path = thisProcess.platform.recordingsDir +/+ "SC_" ++ Main.elapsedTime.round(0.01) ++ "." ++ recHeaderFormat;
922 path = thisProcess.platform.recordingsDir +/+ "SC_" ++ Date.localtime.stamp ++ "." ++ recHeaderFormat;
925 recordBuf = Buffer.alloc(this, 65536, recChannels,
926 {arg buf; buf.writeMsg(path, recHeaderFormat, recSampleFormat, 0, 0, true);},
927 this.options.numBuffers + 1); // prevent buffer conflicts by using reserved bufnum
928 recordBuf.path = path;
929 SynthDef("server-record", { arg bufnum;
930 DiskOut.ar(bufnum, In.ar(0, recChannels))
934 // CmdPeriod support for Server-scope and Server-record and Server-volume
937 this.changed(\cmdPeriod);
941 if(scopeWindow.notNil) { scopeWindow.run }
944 defaultGroup { ^Group.basicNew(this, 1) }
946 queryAllNodes { arg queryControls = false;
947 var resp, done = false;
948 if(isLocal, {this.sendMsg("/g_dumpTree", 0, queryControls.binaryValue);}, {
949 resp = OSCFunc({ arg msg;
950 var i = 2, tabs = 0, printControls = false, dumpFunc;
951 if(msg[1] != 0, {printControls = true});
952 ("NODE TREE Group" + msg[2]).postln;
954 dumpFunc = {|numChildren|
958 if(msg[i + 1] >=0, {i = i + 2}, {
959 i = i + 3 + if(printControls, {msg[i + 3] * 2 + 1}, {0});
961 tabs.do({ " ".post });
962 msg[i].post; // nodeID
965 if(msg[i + 1] > 0, { dumpFunc.value(msg[i + 1]) });
967 (" " ++ msg[i + 2]).postln; // defname
971 tabs.do({ " ".post });
976 if(msg[i + 4 + j].isMemberOf(Symbol), {
977 (msg[i + 4 + j] ++ ": ").post;
988 dumpFunc.value(msg[3]);
991 }, '/g_queryTree.reply', addr).oneShot;
992 this.sendMsg("/g_queryTree", 0, queryControls.binaryValue);
993 SystemClock.sched(3, {
996 "Remote server failed to respond to queryAllNodes!".warn;
1004 storeOn { arg stream;
1005 var codeStr = this.switch (
1006 Server.default, { if (sync_s) { "s" } { "Server.default" } },
1007 Server.local, { "Server.local" },
1008 Server.internal, { "Server.internal" },
1009 { "Server.fromName(" + name.asCompileString + ")" }
1014 archiveAsCompileString { ^true }
1015 archiveAsObject { ^true }
1017 volume_ {arg newVolume;
1018 volume.volume_(newVolume);
1029 hasShmInterface { ^serverInterface.notNil }
1031 reorder { arg nodeList, target, addAction=\addToHead;
1032 target = target.asTarget;
1033 this.sendMsg(62, Node.actionNumberFor(addAction), target.nodeID, *(nodeList.collect(_.nodeID))); //"/n_order"
1036 getControlBusValue {|busIndex|
1037 if (serverInterface.isNil) {
1038 Error("Server-getControlBusValue only supports local servers").throw;
1040 ^serverInterface.getControlBusValue(busIndex)
1044 getControlBusValues {|busIndex, busChannels|
1045 if (serverInterface.isNil) {
1046 Error("Server-getControlBusValues only supports local servers").throw;
1048 ^serverInterface.getControlBusValues(busIndex, busChannels)
1052 setControlBusValue {|busIndex, value|
1053 if (serverInterface.isNil) {
1054 Error("Server-getControlBusValue only supports local servers").throw;
1056 ^serverInterface.setControlBusValue(busIndex, value)
1060 setControlBusValues {|busIndex, valueArray|
1061 if (serverInterface.isNil) {
1062 Error("Server-getControlBusValues only supports local servers").throw;
1064 ^serverInterface.setControlBusValues(busIndex, valueArray)