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 (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
154 numAudioBusChannels_{|num|
155 this.numPrivateAudioBusChannels = num - (numInputBusChannels + numOutputBusChannels);
156 this.deprecated(thisMethod);
160 ^numPrivateAudioBusChannels + numInputBusChannels + numOutputBusChannels
165 ^this.primitiveFailed
170 this.deprecated(thisMethod, ServerOptions.findMethod(\zeroConf_))
174 this.deprecated(thisMethod, ServerOptions.findMethod(\zeroConf));
181 ^this.primitiveFailed
185 ^this.prListDevices(1, 1);
189 ^this.prListDevices(1, 0);
193 ^this.prListDevices(0, 1);
202 ^super.new.connect(port)
206 _ServerShmInterface_connectSharedMem
207 ^this.primitiveFailed
211 _ServerShmInterface_disconnectSharedMem
212 ^this.primitiveFailed
216 _ServerShmInterface_getControlBusValue
217 ^this.primitiveFailed
220 getControlBusValues {
221 _ServerShmInterface_getControlBusValues
222 ^this.primitiveFailed
226 _ServerShmInterface_getControlBusValue
227 ^this.primitiveFailed
230 setControlBusValues {
231 _ServerShmInterface_getControlBusValues
232 ^this.primitiveFailed
237 classvar <>local, <>internal, <default, <>named, <>set, <>program, <>sync_s = true;
239 var <name, <>addr, <clientID=0;
240 var <isLocal, <inProcess, <>sendQuit, <>remoteControlled;
241 var <serverRunning = false, <serverBooting = false, bootNotifyFirst = false;
242 var <>options, <>latency = 0.2, <dumpMode = 0, <notify = true, <notified=false;
244 var <controlBusAllocator;
245 var <audioBusAllocator;
246 var <bufferAllocator;
247 var <scopeBufferAllocator;
248 var <syncThread, <syncTasks;
250 var <numUGens=0, <numSynths=0, <numGroups=0, <numSynthDefs=0;
251 var <avgCPU, <peakCPU;
252 var <sampleRate, <actualSampleRate;
254 var alive = false, booting = false, aliveThread, <>aliveThreadPeriod = 0.7, statusWatcher;
257 var <window, <>scopeWindow;
259 var recordBuf, <recordNode, <>recHeaderFormat="aiff", <>recSampleFormat="float";
268 default = server; // sync with s?
269 if (sync_s, { thisProcess.interpreter.s = server });
270 this.all.do(_.changed(\default));
273 *new { arg name, addr, options, clientID=0;
274 ^super.new.init(name, addr, options, clientID)
278 *all_ { arg dict; set = dict }
280 init { arg argName, argAddr, argOptions, argClientID;
281 name = argName.asSymbol;
283 clientID = argClientID;
284 options = argOptions ? ServerOptions.new;
285 if (addr.isNil, { addr = NetAddr("127.0.0.1", 57110) });
286 inProcess = addr.addr == 0;
287 isLocal = inProcess || { addr.addr == 2130706433 };
288 remoteControlled = isLocal;
289 serverRunning = false;
290 named.put(name, this);
293 Server.changed(\serverAdded, this);
294 volume = Volume.new(server: this, persist: true);
298 nodeAllocator = NodeIDAllocator(clientID, options.initialNodeID);
299 this.sendMsg("/g_new", 1, 0, 0);
301 ServerTree.run(this);
304 this.newNodeAllocators;
305 this.newBusAllocators;
306 this.newBufferAllocators;
307 this.newScopeBufferAllocators;
308 NotificationCenter.notify(this, \newAllocators);
312 nodeAllocator = NodeIDAllocator(clientID, options.initialNodeID);
316 controlBusAllocator = ContiguousBlockAllocator.new(options.numControlBusChannels);
317 audioBusAllocator = ContiguousBlockAllocator.new(options.numAudioBusChannels,
318 options.firstPrivateBus);
321 newBufferAllocators {
322 bufferAllocator = ContiguousBlockAllocator.new(options.numBuffers);
325 newScopeBufferAllocators {
327 scopeBufferAllocator = StackNumberAllocator.new(0, 127);
335 ^nodeAllocator.allocPerm
337 freePermNodeID { |id|
338 ^nodeAllocator.freePerm(id)
342 Class.initClassTree(ServerOptions);
343 Class.initClassTree(NotificationCenter);
344 named = IdentityDictionary.new;
346 default = local = Server.new(\localhost, NetAddr("127.0.0.1", 57110));
347 Platform.switch(\windows, {
348 program = "scsynth.exe";
350 internal = Server.new(\internal, NetAddr.new);
351 program = "cd % && exec ./scsynth".format(String.scDir.quote);
355 *fromName { arg name;
356 ^Server.named[name] ?? {
357 Server(name, NetAddr.new("127.0.0.1", 57110), ServerOptions.new, 0)
361 // bundling support added
362 sendMsg { arg ... msg;
365 sendBundle { arg time ... msgs;
366 addr.sendBundle(time, *msgs)
369 sendRaw { arg rawArray;
370 addr.sendRaw(rawArray);
373 sendMsgSync { arg condition ... args;
375 if (condition.isNil) { condition = Condition.new };
376 cmdName = args[0].asString;
377 if (cmdName[0] != $/) { cmdName = cmdName.insert(0, $/) };
378 resp = OSCFunc({|msg|
379 if (msg[1].asString == cmdName) {
381 condition.test = true;
385 condition.test = false;
386 addr.sendBundle(nil, args);
390 sync { arg condition, bundles, latency; // array of bundles that cause async action
391 addr.sync(condition, bundles, latency)
394 schedSync { arg func;
395 syncTasks = syncTasks.add(func);
396 if(syncThread.isNil) {
397 syncThread = Routine.run {
398 var c; c = Condition.new;
399 while { syncTasks.notEmpty } { syncTasks.removeAt(0).value(c) };
406 // bundling support added
407 listSendMsg { arg msg;
410 listSendBundle { arg time, msgs;
411 addr.sendBundle(time, *(msgs.asArray));
414 // load from disk locally, send remote
415 sendSynthDef { arg name, dir;
417 dir = dir ? SynthDef.synthDefDir;
418 file = File(dir ++ name ++ ".scsyndef","r");
419 if (file.isNil, { ^nil });
421 buffer = Int8Array.newClear(file.length);
426 this.sendMsg("/d_recv", buffer);
428 // tell server to load from disk
429 loadSynthDef { arg name, completionMsg, dir;
430 dir = dir ? SynthDef.synthDefDir;
432 ["/d_load", dir ++ name ++ ".scsyndef", completionMsg ]
436 loadDirectory { arg dir, completionMsg;
437 this.listSendMsg(["/d_loadDir", dir, completionMsg]);
440 serverRunning_ { arg val;
442 { this.changed(\bundling); }.defer;
444 if (val != serverRunning) {
445 if(thisProcess.platform.isSleeping.not) {
447 if (serverRunning.not) {
449 ServerQuit.run(this);
451 AppClock.sched(5.0, {
452 // still down after 5 seconds, assume server is really dead
453 // if you explicitly shut down the server then newAllocators
454 // and the \newAllocators notification will happen immediately
455 if(serverRunning.not) {
456 NotificationCenter.notify(this,\didQuit);
459 if(this.isLocal.not){
465 ServerBoot.run(this);
467 { this.changed(\serverRunning); }.defer;
473 wait { arg responseName;
475 routine = thisThread;
477 routine.resume(true);
478 }, responseName, addr).oneShot;
481 waitForBoot { arg onComplete, limit=100;
482 if(serverRunning.not) { this.boot };
483 this.doWhenBooted(onComplete, limit);
486 doWhenBooted { arg onComplete, limit=100;
487 var mBootNotifyFirst = bootNotifyFirst;
488 bootNotifyFirst = false;
493 or: (serverBooting and: mBootNotifyFirst.not))
494 and: {(limit = limit - 1) > 0})
495 and: { pid.tryPerform(\pidRunning) == true }
500 if(serverRunning.not,{
501 "server failed to start".error;
502 "For advice: [http://supercollider.sf.net/wiki/index.php/ERROR:_server_failed_to_start]".postln;
503 serverBooting = false;
508 bootSync { arg condition;
509 condition ?? { condition = Condition.new };
510 condition.test = false;
512 // Setting func to true indicates that our condition has become true and we can go when signaled.
513 condition.test = true;
519 ping { arg n=1, wait=0.1, func;
520 var result=0, pingFunc;
521 if(serverRunning.not) { "server not running".postln; ^this };
525 t = Main.elapsedTime;
527 dt = Main.elapsedTime - t;
528 ("measured latency:" + dt + "s").postln;
529 result = max(result, dt);
532 SystemClock.sched(wait, {pingFunc.value; nil })
534 ("maximum determined latency of" + name + ":" + result + "s").postln;
543 if(statusWatcher.isNil) {
549 this.sendNotifyRequest;
550 "Receiving notification messages from server %\n".postf(this.name);
554 #cmd, one, numUGens, numSynths, numGroups, numSynthDefs,
555 avgCPU, peakCPU, sampleRate, actualSampleRate = msg;
557 this.serverRunning_(true);
558 this.changed(\counts);
561 }, '/status.reply', addr).fix;
563 statusWatcher.enable;
566 // Buffer objects are cached in an Array for easy
567 // auto buffer info updating
569 this.deprecated(thisMethod, Buffer.findRespondingMethodFor(\cache));
575 this.deprecated(thisMethod, Buffer.findRespondingMethodFor(\uncache));
576 if((buf = Buffer.cachedBufferAt(this, i)).notNil) { buf.free };
579 // /b_info on the way
580 // keeps a reference count of waiting Buffers so that only one responder is needed
582 this.deprecated(thisMethod, Buffer.findRespondingMethodFor(\cache));
585 resetBufferAutoInfo {
586 this.deprecated(thisMethod, Meta_Buffer.findRespondingMethodFor(\clearServerCaches));
587 Buffer.clearServerCaches(this);
590 cachedBuffersDo { |func| Buffer.cachedBuffersDo(this, func) }
591 cachedBufferAt { |bufnum| ^Buffer.cachedBufferAt(this, bufnum) }
594 ^Bus(\audio, this.options.numOutputBusChannels, this.options.numInputBusChannels, this);
598 ^Bus(\audio, 0, this.options.numOutputBusChannels, this);
601 startAliveThread { arg delay=0.0;
602 this.addStatusWatcher;
604 aliveThread = Routine({
605 // this thread polls the server to see if it is alive
609 aliveThreadPeriod.wait;
610 this.serverRunning = alive;
614 AppClock.play(aliveThread);
619 if( aliveThread.notNil, {
623 if( statusWatcher.notNil, {
628 aliveThreadIsRunning {
629 ^aliveThread.notNil and: {aliveThread.isPlaying}
633 server.stopAliveThread;
634 server.startAliveThread(server.aliveThreadPeriod);
638 boot { arg startAliveThread=true, recover=false;
640 if (serverRunning, { "server already running".inform; ^this });
641 if (serverBooting, { "server already booting".inform; ^this });
643 serverBooting = true;
644 if(startAliveThread, { this.startAliveThread });
645 if(recover) { this.newNodeAllocators } { this.newAllocators };
646 bootNotifyFirst = true;
648 serverBooting = false;
649 if (sendQuit.isNil) {
650 sendQuit = not(this.inProcess) and: {this.isLocal};
653 if (this.inProcess) {
654 serverInterface = ServerShmInterface(thisProcess.pid);
657 serverInterface = ServerShmInterface(addr.port);
662 (volume.volume != 0.0).if({
666 if (remoteControlled.not, {
667 "You will have to manually boot remote server.".inform;
675 "booting internal".inform;
678 //this.serverRunning = true;
679 pid = thisProcess.pid;
681 if (serverInterface.notNil) {
682 serverInterface.disconnect;
683 serverInterface = nil;
686 pid = (program ++ options.asOptionsString(addr.port)).unixCmd;
687 //unixCmd(program ++ options.asOptionsString(addr.port)).postln;
688 ("booting " ++ addr.port.asString).inform;
692 reboot { arg func; // func is evaluated when server is off
693 if (isLocal.not) { "can't reboot a remote server".inform; ^this };
712 notify_ { |flag = true|
716 this.sendNotifyRequest(true);
718 "Receiving notification messages from server %\n".postf(this.name);
721 this.sendNotifyRequest(false);
723 "Switched off notification messages from server %\n".postf(this.name);
727 sendNotifyRequest { arg flag=true;
729 addr.sendMsg("/notify", flag.binaryValue);
732 dumpOSC { arg code=1;
734 0 - turn dumping OFF.
735 1 - print the parsed contents of the message.
736 2 - print the contents in hexadecimal.
737 3 - print both the parsed and hexadecimal representations of the contents.
740 this.sendMsg(\dumpOSC,code);
744 var serverReallyQuitWatcher, serverReallyQuit = false;
746 statusWatcher.disable;
748 serverReallyQuitWatcher = OSCFunc({ |msg|
749 if(msg[1] == '/quit') {
750 statusWatcher.enable;
751 serverReallyQuit = true;
752 serverReallyQuitWatcher.free;
755 // don't accumulate quit-watchers if /done doesn't come back
756 AppClock.sched(3.0, {
757 if(serverReallyQuit.not) {
758 "Server % failed to quit after 3.0 seconds.".format(this.name).warn;
759 serverReallyQuitWatcher.free;
764 addr.sendMsg("/quit");
767 "quit done\n".inform;
769 "/quit sent\n".inform;
775 serverBooting = false;
777 this.serverRunning = false;
778 if(scopeWindow.notNil) { scopeWindow.quit };
779 RootNode(this).freeAll;
780 volume.isPlaying.if({
788 if (server.sendQuit === true) {
794 // if you see Exception in World_OpenUDP: unable to bind udp socket
795 // its because you have multiple servers running, left
796 // over from crashes, unexpected quits etc.
797 // you can't cause them to quit via OSC (the boot button)
799 // this brutally kills them all off
800 thisProcess.platform.killAll(this.program.basename);
804 this.sendMsg("/g_freeAll", 0);
805 this.sendMsg("/clearSched");
809 *freeAll { arg evenRemote = false;
812 if ( server.serverRunning ) { server.freeAll }
816 if (server.isLocal and:{ server.serverRunning }) { server.freeAll }
821 *hardFreeAll { arg evenRemote = false;
828 if (server.isLocal) { server.freeAll }
834 ^this.all.select(_.serverRunning)
840 openBundle { arg bundle; // pass in a bundle that you want to
841 // continue adding to, or nil for a new bundle.
843 bundle = addr.bundle.addAll(bundle);
844 addr.bundle = []; // debatable
846 addr = BundleNetAddr.copyFrom(addr, bundle);
848 closeBundle { arg time; // set time to false if you don't want to send.
851 bundle = addr.closeBundle(time);
852 addr = addr.saveAddr;
854 "there is no open bundle.".warn
858 makeBundle { arg time, func, bundle;
859 this.openBundle(bundle);
862 bundle = this.closeBundle(time);
864 addr = addr.saveAddr; // on error restore the normal NetAddr
870 ^this.makeBundle(this.latency, func)
873 // internal server commands
875 ^options.bootInProcess;
879 ^this.primitiveFailed
881 allocSharedControls { arg numControls=1024;
883 ^this.primitiveFailed
885 setSharedControl { arg num, value;
887 ^this.primitiveFailed
889 getSharedControl { arg num;
891 ^this.primitiveFailed
897 this.prepareForRecord(path);
903 if(recordNode.isNil){
904 recordNode = Synth.tail(RootNode(this), "server-record",
905 [\bufnum, recordBuf.bufnum]);
908 if (recordBuf.notNil) { recordBuf.close {|buf| buf.free; }; recordBuf = nil; };
913 "Recording: %\n".postf(recordBuf.path);
918 recordNode.notNil.if({ recordNode.run(false); "Paused".postln }, { "Not Recording".warn });
922 if(recordNode.notNil) {
925 recordBuf.close({ arg buf; buf.free; });
926 "Recording Stopped: %\n".postf(recordBuf.path);
933 prepareForRecord { arg path;
935 if(File.exists(thisProcess.platform.recordingsDir).not) {
936 thisProcess.platform.recordingsDir.mkdir
939 // temporary kludge to fix Date's brokenness on windows
940 if(thisProcess.platform.name == \windows) {
941 path = thisProcess.platform.recordingsDir +/+ "SC_" ++ Main.elapsedTime.round(0.01) ++ "." ++ recHeaderFormat;
944 path = thisProcess.platform.recordingsDir +/+ "SC_" ++ Date.localtime.stamp ++ "." ++ recHeaderFormat;
947 recordBuf = Buffer.alloc(this, 65536, recChannels,
948 {arg buf; buf.writeMsg(path, recHeaderFormat, recSampleFormat, 0, 0, true);},
949 this.options.numBuffers + 1); // prevent buffer conflicts by using reserved bufnum
950 recordBuf.path = path;
951 SynthDef("server-record", { arg bufnum;
952 DiskOut.ar(bufnum, In.ar(0, recChannels))
956 // CmdPeriod support for Server-scope and Server-record and Server-volume
959 this.changed(\cmdPeriod);
963 if(scopeWindow.notNil) { scopeWindow.run }
966 defaultGroup { ^Group.basicNew(this, 1) }
968 queryAllNodes { arg queryControls = false;
969 var resp, done = false;
970 if(isLocal, {this.sendMsg("/g_dumpTree", 0, queryControls.binaryValue);}, {
971 resp = OSCFunc({ arg msg;
972 var i = 2, tabs = 0, printControls = false, dumpFunc;
973 if(msg[1] != 0, {printControls = true});
974 ("NODE TREE Group" + msg[2]).postln;
976 dumpFunc = {|numChildren|
980 if(msg[i + 1] >=0, {i = i + 2}, {
981 i = i + 3 + if(printControls, {msg[i + 3] * 2 + 1}, {0});
983 tabs.do({ " ".post });
984 msg[i].post; // nodeID
987 if(msg[i + 1] > 0, { dumpFunc.value(msg[i + 1]) });
989 (" " ++ msg[i + 2]).postln; // defname
993 tabs.do({ " ".post });
998 if(msg[i + 4 + j].isMemberOf(Symbol), {
999 (msg[i + 4 + j] ++ ": ").post;
1001 msg[i + 5 + j].post;
1010 dumpFunc.value(msg[3]);
1013 }, '/g_queryTree.reply', addr).oneShot;
1014 this.sendMsg("/g_queryTree", 0, queryControls.binaryValue);
1015 SystemClock.sched(3, {
1018 "Remote server failed to respond to queryAllNodes!".warn;
1026 storeOn { arg stream;
1027 var codeStr = this.switch (
1028 Server.default, { if (sync_s) { "s" } { "Server.default" } },
1029 Server.local, { "Server.local" },
1030 Server.internal, { "Server.internal" },
1031 { "Server.fromName(" + name.asCompileString + ")" }
1036 archiveAsCompileString { ^true }
1037 archiveAsObject { ^true }
1039 volume_ {arg newVolume;
1040 volume.volume_(newVolume);
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")
1060 ^serverInterface.getControlBusValue(busIndex)
1064 getControlBusValues {|busIndex, busChannels|
1065 if (serverInterface.isNil) {
1066 error("Server-getControlBusValues only supports local servers")
1068 ^serverInterface.getControlBusValues(busIndex, busChannels)
1072 setControlBusValue {|busIndex, value|
1073 if (serverInterface.isNil) {
1074 error("Server-getControlBusValue only supports local servers")
1076 ^serverInterface.setControlBusValue(busIndex, value)
1080 setControlBusValues {|busIndex, valueArray|
1081 if (serverInterface.isNil) {
1082 error("Server-getControlBusValues only supports local servers")
1084 ^serverInterface.setControlBusValues(busIndex, valueArray)