3 // order of variables is important here. Only add new instance variables to the end.
4 var <numAudioBusChannels=128;
5 var <>numControlBusChannels=4096;
6 var <numInputBusChannels=8;
7 var <numOutputBusChannels=8;
11 var <>maxSynthDefs=1024;
12 var <>protocol = \udp;
14 var <>hardwareBufferSize = nil;
18 var <>numWireBufs = 64;
20 var <>sampleRate = nil;
21 var <>loadDefs = true;
23 var <>inputStreamsEnabled;
24 var <>outputStreamsEnabled;
27 var <>outDevice = nil;
30 var <>zeroConf = false; // Whether server publishes port to Bonjour, etc.
32 var <>restrictedPath = nil;
34 var <>initialNodeID = 1000;
35 var <>remoteControlVolume = false;
37 var <>memoryLocking = false;
38 var <>threads = nil; // for supernova
40 var <numPrivateAudioBusChannels=112;
43 ^if(inDevice == outDevice)
54 inDevice = outDevice = dev;
60 // prevent buffer conflicts in Server-prepareForRecord and Server-scope by
61 // ensuring reserved buffers
67 numBuffers_ { | argNumBuffers |
68 numBuffers = argNumBuffers + 2
71 asOptionsString { | port = 57110 |
73 o = if (protocol == \tcp, " -t ", " -u ");
76 o = o ++ " -a " ++ (numPrivateAudioBusChannels + numInputBusChannels + numOutputBusChannels) ;
78 if (numControlBusChannels != 4096, {
79 o = o ++ " -c " ++ numControlBusChannels;
81 if (numInputBusChannels != 8, {
82 o = o ++ " -i " ++ numInputBusChannels;
84 if (numOutputBusChannels != 8, {
85 o = o ++ " -o " ++ numOutputBusChannels;
87 if (numBuffers != 1024, {
88 o = o ++ " -b " ++ numBuffers;
90 if (maxNodes != 1024, {
91 o = o ++ " -n " ++ maxNodes;
93 if (maxSynthDefs != 1024, {
94 o = o ++ " -d " ++ maxSynthDefs;
96 if (blockSize != 64, {
97 o = o ++ " -z " ++ blockSize;
99 if (hardwareBufferSize.notNil, {
100 o = o ++ " -Z " ++ hardwareBufferSize;
102 if (memSize != 8192, {
103 o = o ++ " -m " ++ memSize;
105 if (numRGens != 64, {
106 o = o ++ " -r " ++ numRGens;
108 if (numWireBufs != 64, {
109 o = o ++ " -w " ++ numWireBufs;
111 if (sampleRate.notNil, {
112 o = o ++ " -S " ++ sampleRate;
117 if (inputStreamsEnabled.notNil, {
118 o = o ++ " -I " ++ inputStreamsEnabled ;
120 if (outputStreamsEnabled.notNil, {
121 o = o ++ " -O " ++ outputStreamsEnabled ;
123 if ((thisProcess.platform.name!=\osx) or: {inDevice == outDevice})
127 o = o ++ " -H %".format(inDevice.quote);
131 o = o ++ " -H % %".format(inDevice.asString.quote, outDevice.asString.quote);
133 if (verbosity != 0, {
134 o = o ++ " -v " ++ verbosity;
139 if (restrictedPath.notNil, {
140 o = o ++ " -P " ++ restrictedPath;
145 if (threads.notNil, {
146 if (Server.program.asString.endsWith("supernova")) {
147 o = o ++ " -T " ++ threads;
153 firstPrivateBus { // after the outs and ins
154 ^numOutputBusChannels + numInputBusChannels
159 ^this.primitiveFailed
162 numPrivateAudioBusChannels_ {arg numChannels = 112;
163 numPrivateAudioBusChannels = numChannels;
167 numAudioBusChannels_ {arg numChannels=128;
168 numAudioBusChannels = numChannels;
169 numPrivateAudioBusChannels = numAudioBusChannels - numInputBusChannels - numOutputBusChannels;
172 numInputBusChannels_ {arg numChannels=8;
173 numInputBusChannels = numChannels;
177 numOutputBusChannels_ {arg numChannels=8;
178 numOutputBusChannels = numChannels;
183 numAudioBusChannels = numPrivateAudioBusChannels + numInputBusChannels + numOutputBusChannels;
189 ^this.primitiveFailed
193 ^this.prListDevices(1, 1);
197 ^this.prListDevices(1, 0);
201 ^this.prListDevices(0, 1);
210 ^super.new.connect(port)
214 // never ever copy! will cause duplicate calls to the finalizer!
219 _ServerShmInterface_connectSharedMem
220 ^this.primitiveFailed
224 _ServerShmInterface_disconnectSharedMem
225 ^this.primitiveFailed
229 _ServerShmInterface_getControlBusValue
230 ^this.primitiveFailed
233 getControlBusValues {
234 _ServerShmInterface_getControlBusValues
235 ^this.primitiveFailed
239 _ServerShmInterface_setControlBusValue
240 ^this.primitiveFailed
243 setControlBusValues {
244 _ServerShmInterface_setControlBusValues
245 ^this.primitiveFailed
250 classvar <>local, <>internal, <default, <>named, <>set, <>program, <>sync_s = true;
252 var <name, <>addr, <clientID=0;
253 var <isLocal, <inProcess, <>sendQuit, <>remoteControlled;
254 var <serverRunning = false, <serverBooting = false, bootNotifyFirst = false;
255 var <>options, <>latency = 0.2, <dumpMode = 0, <notify = true, <notified=false;
257 var <controlBusAllocator;
258 var <audioBusAllocator;
259 var <bufferAllocator;
260 var <scopeBufferAllocator;
261 var <syncThread, <syncTasks;
263 var <numUGens=0, <numSynths=0, <numGroups=0, <numSynthDefs=0;
264 var <avgCPU, <peakCPU;
265 var <sampleRate, <actualSampleRate;
267 var alive = false, booting = false, aliveThread, <>aliveThreadPeriod = 0.7, statusWatcher;
270 var <window, <>scopeWindow;
272 var recordBuf, <recordNode, <>recHeaderFormat="aiff", <>recSampleFormat="float";
281 default = server; // sync with s?
282 if (sync_s, { thisProcess.interpreter.s = server });
283 this.all.do(_.changed(\default));
286 *new { arg name, addr, options, clientID=0;
287 ^super.new.init(name, addr, options, clientID)
291 *all_ { arg dict; set = dict }
293 init { arg argName, argAddr, argOptions, argClientID;
294 name = argName.asSymbol;
296 clientID = argClientID;
297 options = argOptions ? ServerOptions.new;
298 if (addr.isNil, { addr = NetAddr("127.0.0.1", 57110) });
299 inProcess = addr.addr == 0;
300 isLocal = inProcess || { addr.addr == 2130706433 };
301 remoteControlled = isLocal;
302 serverRunning = false;
303 named.put(name, this);
306 Server.changed(\serverAdded, this);
307 volume = Volume.new(server: this, persist: true);
311 nodeAllocator = NodeIDAllocator(clientID, options.initialNodeID);
312 this.sendMsg("/g_new", 1, 0, 0);
314 ServerTree.run(this);
317 this.newNodeAllocators;
318 this.newBusAllocators;
319 this.newBufferAllocators;
320 this.newScopeBufferAllocators;
321 NotificationCenter.notify(this, \newAllocators);
325 nodeAllocator = NodeIDAllocator(clientID, options.initialNodeID);
329 controlBusAllocator = ContiguousBlockAllocator.new(options.numControlBusChannels);
330 audioBusAllocator = ContiguousBlockAllocator.new(options.numAudioBusChannels,
331 options.firstPrivateBus);
334 newBufferAllocators {
335 bufferAllocator = ContiguousBlockAllocator.new(options.numBuffers);
338 newScopeBufferAllocators {
340 scopeBufferAllocator = StackNumberAllocator.new(0, 127);
348 ^nodeAllocator.allocPerm
350 freePermNodeID { |id|
351 ^nodeAllocator.freePerm(id)
355 Class.initClassTree(ServerOptions);
356 Class.initClassTree(NotificationCenter);
357 named = IdentityDictionary.new;
359 default = local = Server.new(\localhost, NetAddr("127.0.0.1", 57110));
360 internal = Server.new(\internal, NetAddr.new);
363 *fromName { arg name;
364 ^Server.named[name] ?? {
365 Server(name, NetAddr.new("127.0.0.1", 57110), ServerOptions.new, 0)
369 // bundling support added
370 sendMsg { arg ... msg;
373 sendBundle { arg time ... msgs;
374 addr.sendBundle(time, *msgs)
377 sendRaw { arg rawArray;
378 addr.sendRaw(rawArray);
381 sendMsgSync { arg condition ... args;
383 if (condition.isNil) { condition = Condition.new };
384 cmdName = args[0].asString;
385 if (cmdName[0] != $/) { cmdName = cmdName.insert(0, $/) };
386 resp = OSCFunc({|msg|
387 if (msg[1].asString == cmdName) {
389 condition.test = true;
393 condition.test = false;
394 addr.sendBundle(nil, args);
398 sync { arg condition, bundles, latency; // array of bundles that cause async action
399 addr.sync(condition, bundles, latency)
402 schedSync { arg func;
403 syncTasks = syncTasks.add(func);
404 if(syncThread.isNil) {
405 syncThread = Routine.run {
406 var c; c = Condition.new;
407 while { syncTasks.notEmpty } { syncTasks.removeAt(0).value(c) };
414 // bundling support added
415 listSendMsg { arg msg;
418 listSendBundle { arg time, msgs;
419 addr.sendBundle(time, *(msgs.asArray));
422 // load from disk locally, send remote
423 sendSynthDef { arg name, dir;
425 dir = dir ? SynthDef.synthDefDir;
426 file = File(dir ++ name ++ ".scsyndef","r");
427 if (file.isNil, { ^nil });
429 buffer = Int8Array.newClear(file.length);
434 this.sendMsg("/d_recv", buffer);
436 // tell server to load from disk
437 loadSynthDef { arg name, completionMsg, dir;
438 dir = dir ? SynthDef.synthDefDir;
440 ["/d_load", dir ++ name ++ ".scsyndef", completionMsg ]
444 loadDirectory { arg dir, completionMsg;
445 this.listSendMsg(["/d_loadDir", dir, completionMsg]);
448 serverRunning_ { arg val;
450 { this.changed(\bundling); }.defer;
452 if (val != serverRunning) {
453 if(thisProcess.platform.isSleeping.not) {
455 if (serverRunning.not) {
457 ServerQuit.run(this);
459 if (serverInterface.notNil) {
460 serverInterface.disconnect;
461 serverInterface = nil;
464 AppClock.sched(5.0, {
465 // still down after 5 seconds, assume server is really dead
466 // if you explicitly shut down the server then newAllocators
467 // and the \newAllocators notification will happen immediately
468 if(serverRunning.not) {
469 NotificationCenter.notify(this,\didQuit);
472 if(this.isLocal.not){
478 ServerBoot.run(this);
480 { this.changed(\serverRunning); }.defer;
486 wait { arg responseName;
488 routine = thisThread;
490 routine.resume(true);
491 }, responseName, addr).oneShot;
494 waitForBoot { arg onComplete, limit=100, onFailure;
495 // onFailure.true: why is this necessary?
496 // this.boot also calls doWhenBooted.
497 // doWhenBooted prints the normal boot failure message.
498 // if the server fails to boot, the failure error gets posted TWICE.
499 // So, we suppress one of them.
500 if(serverRunning.not) { this.boot(onFailure: true) };
501 this.doWhenBooted(onComplete, limit, onFailure);
504 doWhenBooted { arg onComplete, limit=100, onFailure;
505 var mBootNotifyFirst = bootNotifyFirst, postError = true;
506 bootNotifyFirst = false;
511 or: (serverBooting and: mBootNotifyFirst.not))
512 and: {(limit = limit - 1) > 0})
513 and: { pid.tryPerform(\pidRunning) == true }
518 if(serverRunning.not,{
519 if(onFailure.notNil) {
520 postError = (onFailure.value == false);
523 "server failed to start".error;
524 "For advice: [http://supercollider.sf.net/wiki/index.php/ERROR:_server_failed_to_start]".postln;
526 serverBooting = false;
527 this.changed(\serverRunning);
532 bootSync { arg condition;
533 condition ?? { condition = Condition.new };
534 condition.test = false;
536 // Setting func to true indicates that our condition has become true and we can go when signaled.
537 condition.test = true;
543 ping { arg n=1, wait=0.1, func;
544 var result=0, pingFunc;
545 if(serverRunning.not) { "server not running".postln; ^this };
549 t = Main.elapsedTime;
551 dt = Main.elapsedTime - t;
552 ("measured latency:" + dt + "s").postln;
553 result = max(result, dt);
556 SystemClock.sched(wait, {pingFunc.value; nil })
558 ("maximum determined latency of" + name + ":" + result + "s").postln;
567 if(statusWatcher.isNil) {
573 this.sendNotifyRequest;
574 "Receiving notification messages from server %\n".postf(this.name);
578 #cmd, one, numUGens, numSynths, numGroups, numSynthDefs,
579 avgCPU, peakCPU, sampleRate, actualSampleRate = msg;
581 this.serverRunning_(true);
582 this.changed(\counts);
585 }, '/status.reply', addr).fix;
587 statusWatcher.enable;
591 cachedBuffersDo { |func| Buffer.cachedBuffersDo(this, func) }
592 cachedBufferAt { |bufnum| ^Buffer.cachedBufferAt(this, bufnum) }
595 ^Bus(\audio, this.options.numOutputBusChannels, this.options.numInputBusChannels, this);
599 ^Bus(\audio, 0, this.options.numOutputBusChannels, this);
602 startAliveThread { arg delay=0.0;
603 this.addStatusWatcher;
605 aliveThread = Routine({
606 // this thread polls the server to see if it is alive
610 aliveThreadPeriod.wait;
611 this.serverRunning = alive;
615 AppClock.play(aliveThread);
620 if( aliveThread.notNil, {
624 if( statusWatcher.notNil, {
629 aliveThreadIsRunning {
630 ^aliveThread.notNil and: {aliveThread.isPlaying}
634 server.stopAliveThread;
635 server.startAliveThread(server.aliveThreadPeriod);
639 boot { arg startAliveThread=true, recover=false, onFailure;
641 if (serverRunning, { "server already running".inform; ^this });
642 if (serverBooting, { "server already booting".inform; ^this });
644 serverBooting = true;
645 if(startAliveThread, { this.startAliveThread });
646 if(recover) { this.newNodeAllocators } { this.newAllocators };
647 bootNotifyFirst = true;
649 serverBooting = false;
650 if (sendQuit.isNil) {
651 sendQuit = this.inProcess or: {this.isLocal};
654 if (this.inProcess) {
655 serverInterface = ServerShmInterface(thisProcess.pid);
658 serverInterface = ServerShmInterface(addr.port);
663 }, onFailure: onFailure ? false);
664 if (remoteControlled.not, {
665 "You will have to manually boot remote server.".inform;
673 "booting internal".inform;
676 //this.serverRunning = true;
677 pid = thisProcess.pid;
679 if (serverInterface.notNil) {
680 serverInterface.disconnect;
681 serverInterface = nil;
684 pid = (program ++ options.asOptionsString(addr.port)).unixCmd;
685 //unixCmd(program ++ options.asOptionsString(addr.port)).postln;
686 ("booting " ++ addr.port.asString).inform;
690 reboot { arg func; // func is evaluated when server is off
691 if (isLocal.not) { "can't reboot a remote server".inform; ^this };
710 notify_ { |flag = true|
714 this.sendNotifyRequest(true);
716 "Receiving notification messages from server %\n".postf(this.name);
719 this.sendNotifyRequest(false);
721 "Switched off notification messages from server %\n".postf(this.name);
725 sendNotifyRequest { arg flag=true;
727 addr.sendMsg("/notify", flag.binaryValue);
730 dumpOSC { arg code=1;
732 0 - turn dumping OFF.
733 1 - print the parsed contents of the message.
734 2 - print the contents in hexadecimal.
735 3 - print both the parsed and hexadecimal representations of the contents.
738 this.sendMsg(\dumpOSC,code);
742 var serverReallyQuitWatcher, serverReallyQuit = false;
744 statusWatcher.disable;
746 serverReallyQuitWatcher = OSCFunc({ |msg|
747 if(msg[1] == '/quit') {
748 statusWatcher.enable;
749 serverReallyQuit = true;
750 serverReallyQuitWatcher.free;
753 // don't accumulate quit-watchers if /done doesn't come back
754 AppClock.sched(3.0, {
755 if(serverReallyQuit.not) {
756 "Server % failed to quit after 3.0 seconds.".format(this.name).warn;
757 serverReallyQuitWatcher.free;
762 addr.sendMsg("/quit");
765 "quit done\n".inform;
767 "/quit sent\n".inform;
773 serverBooting = false;
775 this.serverRunning = false;
776 if(scopeWindow.notNil) { scopeWindow.quit };
777 if(volume.isPlaying) {
780 RootNode(this).freeAll;
786 if (server.sendQuit === true) {
792 // if you see Exception in World_OpenUDP: unable to bind udp socket
793 // its because you have multiple servers running, left
794 // over from crashes, unexpected quits etc.
795 // you can't cause them to quit via OSC (the boot button)
797 // this brutally kills them all off
798 thisProcess.platform.killAll(this.program.basename);
802 this.sendMsg("/g_freeAll", 0);
803 this.sendMsg("/clearSched");
807 *freeAll { arg evenRemote = false;
810 if ( server.serverRunning ) { server.freeAll }
814 if (server.isLocal and:{ server.serverRunning }) { server.freeAll }
819 *hardFreeAll { arg evenRemote = false;
826 if (server.isLocal) { server.freeAll }
832 ^this.all.select(_.serverRunning)
838 openBundle { arg bundle; // pass in a bundle that you want to
839 // continue adding to, or nil for a new bundle.
841 bundle = addr.bundle.addAll(bundle);
842 addr.bundle = []; // debatable
844 addr = BundleNetAddr.copyFrom(addr, bundle);
846 closeBundle { arg time; // set time to false if you don't want to send.
849 bundle = addr.closeBundle(time);
850 addr = addr.saveAddr;
852 "there is no open bundle.".warn
856 makeBundle { arg time, func, bundle;
857 this.openBundle(bundle);
860 bundle = this.closeBundle(time);
862 addr = addr.saveAddr; // on error restore the normal NetAddr
868 ^this.makeBundle(this.latency, func)
871 // internal server commands
873 ^options.bootInProcess;
877 ^this.primitiveFailed
879 allocSharedControls { arg numControls=1024;
881 ^this.primitiveFailed
883 setSharedControl { arg num, value;
885 ^this.primitiveFailed
887 getSharedControl { arg num;
889 ^this.primitiveFailed
895 this.prepareForRecord(path);
901 if(recordNode.isNil){
902 recordNode = Synth.tail(RootNode(this), "server-record",
903 [\bufnum, recordBuf.bufnum]);
906 if (recordBuf.notNil) { recordBuf.close {|buf| buf.freeMsg }; recordBuf = nil; };
911 "Recording: %\n".postf(recordBuf.path);
916 recordNode.notNil.if({ recordNode.run(false); "Paused".postln }, { "Not Recording".warn });
920 if(recordNode.notNil) {
923 recordBuf.close({ |buf| buf.freeMsg });
924 "Recording Stopped: %\n".postf(recordBuf.path);
931 prepareForRecord { arg path;
933 if(File.exists(thisProcess.platform.recordingsDir).not) {
934 thisProcess.platform.recordingsDir.mkdir
937 // temporary kludge to fix Date's brokenness on windows
938 if(thisProcess.platform.name == \windows) {
939 path = thisProcess.platform.recordingsDir +/+ "SC_" ++ Main.elapsedTime.round(0.01) ++ "." ++ recHeaderFormat;
942 path = thisProcess.platform.recordingsDir +/+ "SC_" ++ Date.localtime.stamp ++ "." ++ recHeaderFormat;
945 recordBuf = Buffer.alloc(this, 65536, recChannels,
946 {arg buf; buf.writeMsg(path, recHeaderFormat, recSampleFormat, 0, 0, true);},
947 this.options.numBuffers + 1); // prevent buffer conflicts by using reserved bufnum
948 recordBuf.path = path;
949 SynthDef("server-record", { arg bufnum;
950 DiskOut.ar(bufnum, In.ar(0, recChannels))
954 // CmdPeriod support for Server-scope and Server-record and Server-volume
957 this.changed(\cmdPeriod);
961 if(scopeWindow.notNil) { scopeWindow.run }
964 defaultGroup { ^Group.basicNew(this, 1) }
966 queryAllNodes { arg queryControls = false;
967 var resp, done = false;
968 if(isLocal, {this.sendMsg("/g_dumpTree", 0, queryControls.binaryValue);}, {
969 resp = OSCFunc({ arg msg;
970 var i = 2, tabs = 0, printControls = false, dumpFunc;
971 if(msg[1] != 0, {printControls = true});
972 ("NODE TREE Group" + msg[2]).postln;
974 dumpFunc = {|numChildren|
978 if(msg[i + 1] >=0, {i = i + 2}, {
979 i = i + 3 + if(printControls, {msg[i + 3] * 2 + 1}, {0});
981 tabs.do({ " ".post });
982 msg[i].post; // nodeID
985 if(msg[i + 1] > 0, { dumpFunc.value(msg[i + 1]) });
987 (" " ++ msg[i + 2]).postln; // defname
991 tabs.do({ " ".post });
996 if(msg[i + 4 + j].isMemberOf(Symbol), {
997 (msg[i + 4 + j] ++ ": ").post;
1008 dumpFunc.value(msg[3]);
1011 }, '/g_queryTree.reply', addr).oneShot;
1012 this.sendMsg("/g_queryTree", 0, queryControls.binaryValue);
1013 SystemClock.sched(3, {
1016 "Remote server failed to respond to queryAllNodes!".warn;
1024 storeOn { arg stream;
1025 var codeStr = this.switch (
1026 Server.default, { if (sync_s) { "s" } { "Server.default" } },
1027 Server.local, { "Server.local" },
1028 Server.internal, { "Server.internal" },
1029 { "Server.fromName(" + name.asCompileString + ")" }
1034 archiveAsCompileString { ^true }
1035 archiveAsObject { ^true }
1037 volume_ {arg newVolume;
1038 volume.volume_(newVolume);
1049 hasShmInterface { ^serverInterface.notNil }
1051 reorder { arg nodeList, target, addAction=\addToHead;
1052 target = target.asTarget;
1053 this.sendMsg(62, Node.actionNumberFor(addAction), target.nodeID, *(nodeList.collect(_.nodeID))); //"/n_order"
1056 getControlBusValue {|busIndex|
1057 if (serverInterface.isNil) {
1058 Error("Server-getControlBusValue only supports local servers").throw;
1060 ^serverInterface.getControlBusValue(busIndex)
1064 getControlBusValues {|busIndex, busChannels|
1065 if (serverInterface.isNil) {
1066 Error("Server-getControlBusValues only supports local servers").throw;
1068 ^serverInterface.getControlBusValues(busIndex, busChannels)
1072 setControlBusValue {|busIndex, value|
1073 if (serverInterface.isNil) {
1074 Error("Server-getControlBusValue only supports local servers").throw;
1076 ^serverInterface.setControlBusValue(busIndex, value)
1080 setControlBusValues {|busIndex, valueArray|
1081 if (serverInterface.isNil) {
1082 Error("Server-getControlBusValues only supports local servers").throw;
1084 ^serverInterface.setControlBusValues(busIndex, valueArray)