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;
29 var <>blockAllocClass;
32 var <>zeroConf = false; // Whether server publishes port to Bonjour, etc.
34 var <>restrictedPath = nil;
36 var <>initialNodeID = 1000;
37 var <>remoteControlVolume = false;
39 var <>memoryLocking = false;
43 ^if(inDevice == outDevice)
55 inDevice = outDevice = dev;
61 // prevent buffer conflicts in Server-prepareForRecord and Server-scope by
62 // ensuring reserved buffers
68 numBuffers_ { | argNumBuffers |
69 numBuffers = argNumBuffers + 2
72 asOptionsString { | port = 57110 |
74 o = if (protocol == \tcp, " -t ", " -u ");
77 if (numAudioBusChannels != 128, {
78 o = o ++ " -a " ++ numAudioBusChannels;
80 if (numControlBusChannels != 4096, {
81 o = o ++ " -c " ++ numControlBusChannels;
83 if (numInputBusChannels != 8, {
84 o = o ++ " -i " ++ numInputBusChannels;
86 if (numOutputBusChannels != 8, {
87 o = o ++ " -o " ++ numOutputBusChannels;
89 if (numBuffers != 1024, {
90 o = o ++ " -b " ++ numBuffers;
92 if (maxNodes != 1024, {
93 o = o ++ " -n " ++ maxNodes;
95 if (maxSynthDefs != 1024, {
96 o = o ++ " -d " ++ maxSynthDefs;
98 if (blockSize != 64, {
99 o = o ++ " -z " ++ blockSize;
101 if (hardwareBufferSize.notNil, {
102 o = o ++ " -Z " ++ hardwareBufferSize;
104 if (memSize != 8192, {
105 o = o ++ " -m " ++ memSize;
107 if (numRGens != 64, {
108 o = o ++ " -r " ++ numRGens;
110 if (numWireBufs != 64, {
111 o = o ++ " -w " ++ numWireBufs;
113 if (sampleRate.notNil, {
114 o = o ++ " -S " ++ sampleRate;
119 if (inputStreamsEnabled.notNil, {
120 o = o ++ " -I " ++ inputStreamsEnabled ;
122 if (outputStreamsEnabled.notNil, {
123 o = o ++ " -O " ++ outputStreamsEnabled ;
125 if (inDevice == outDevice)
129 o = o ++ " -H %".format(inDevice.quote);
133 o = o ++ " -H % %".format(inDevice.asString.quote, outDevice.asString.quote);
135 if (verbosity != 0, {
136 o = o ++ " -v " ++ verbosity;
141 if (restrictedPath.notNil, {
142 o = o ++ " -P " ++ restrictedPath;
150 firstPrivateBus { // after the outs and ins
151 ^numOutputBusChannels + numInputBusChannels
156 ^this.primitiveFailed
161 this.deprecated(thisMethod, ServerOptions.findMethod(\zeroConf_))
165 this.deprecated(thisMethod, ServerOptions.findMethod(\zeroConf));
173 ^this.primitiveFailed
178 ^this.prListDevices(1, 1);
183 ^this.prListDevices(1, 0);
188 ^this.prListDevices(0, 1);
193 classvar <>local, <>internal, <default, <>named, <>set, <>program, <>sync_s = true;
195 var <name, <>addr, <clientID=0;
196 var <isLocal, <inProcess, <>sendQuit, <>remoteControlled;
197 var <serverRunning = false, <serverBooting = false, bootNotifyFirst = false;
198 var <>options, <>latency = 0.2, <dumpMode = 0, <notify = true, <notified=false;
200 var <controlBusAllocator;
201 var <audioBusAllocator;
202 var <bufferAllocator;
203 var <syncThread, <syncTasks;
205 var <numUGens=0, <numSynths=0, <numGroups=0, <numSynthDefs=0;
206 var <avgCPU, <peakCPU;
207 var <sampleRate, <actualSampleRate;
209 var alive = false, booting = false, aliveThread, <>aliveThreadPeriod = 0.7, statusWatcher;
212 var <window, <>scopeWindow;
214 var recordBuf, <recordNode, <>recHeaderFormat="aiff", <>recSampleFormat="float"; var <>recChannels=2;
221 default = server; // sync with s?
222 if (sync_s, { thisProcess.interpreter.s = server });
223 this.all.do(_.changed(\default));
226 *new { arg name, addr, options, clientID=0;
227 ^super.new.init(name, addr, options, clientID)
231 *all_ { arg dict; set = dict }
233 init { arg argName, argAddr, argOptions, argClientID;
234 name = argName.asSymbol;
236 clientID = argClientID;
237 options = argOptions ? ServerOptions.new;
238 if (addr.isNil, { addr = NetAddr("127.0.0.1", 57110) });
239 inProcess = addr.addr == 0;
240 isLocal = inProcess || { addr.addr == 2130706433 };
241 remoteControlled = isLocal;
242 serverRunning = false;
243 named.put(name, this);
246 Server.changed(\serverAdded, this);
247 volume = Volume.new(server: this, persist: true);
251 nodeAllocator = NodeIDAllocator(clientID, options.initialNodeID);
252 this.sendMsg("/g_new", 1, 0, 0);
254 ServerTree.run(this);
257 this.newNodeAllocators;
258 this.newBusAllocators;
259 this.newBufferAllocators;
260 NotificationCenter.notify(this, \newAllocators);
264 nodeAllocator = NodeIDAllocator(clientID, options.initialNodeID);
268 controlBusAllocator = ContiguousBlockAllocator.new(options.numControlBusChannels);
269 audioBusAllocator = ContiguousBlockAllocator.new(options.numAudioBusChannels,
270 options.firstPrivateBus);
273 newBufferAllocators {
274 bufferAllocator = ContiguousBlockAllocator.new(options.numBuffers);
281 ^nodeAllocator.allocPerm
283 freePermNodeID { |id|
284 ^nodeAllocator.freePerm(id)
288 Class.initClassTree(ServerOptions);
289 Class.initClassTree(NotificationCenter);
290 named = IdentityDictionary.new;
292 default = local = Server.new(\localhost, NetAddr("127.0.0.1", 57110));
293 Platform.switch(\windows, {
294 program = "scsynth.exe";
296 internal = Server.new(\internal, NetAddr.new);
297 program = "cd % && exec ./scsynth".format(String.scDir.quote);
301 *fromName { arg name;
302 ^Server.named[name] ?? {
303 Server(name, NetAddr.new("127.0.0.1", 57110), ServerOptions.new, 0)
307 // bundling support added
308 sendMsg { arg ... msg;
311 sendBundle { arg time ... msgs;
312 addr.sendBundle(time, *msgs)
315 sendRaw { arg rawArray;
316 addr.sendRaw(rawArray);
319 sendMsgSync { arg condition ... args;
321 if (condition.isNil) { condition = Condition.new };
322 cmdName = args[0].asString;
323 if (cmdName[0] != $/) { cmdName = cmdName.insert(0, $/) };
324 resp = OSCFunc({|msg|
325 if (msg[1].asString == cmdName) {
327 condition.test = true;
331 condition.test = false;
332 addr.sendBundle(nil, args);
336 sync { arg condition, bundles, latency; // array of bundles that cause async action
337 addr.sync(condition, bundles, latency)
340 schedSync { arg func;
341 syncTasks = syncTasks.add(func);
342 if(syncThread.isNil) {
343 syncThread = Routine.run {
344 var c; c = Condition.new;
345 while { syncTasks.notEmpty } { syncTasks.removeAt(0).value(c) };
352 // bundling support added
353 listSendMsg { arg msg;
356 listSendBundle { arg time, msgs;
357 addr.sendBundle(time, *(msgs.asArray));
360 // load from disk locally, send remote
361 sendSynthDef { arg name, dir;
363 dir = dir ? SynthDef.synthDefDir;
364 file = File(dir ++ name ++ ".scsyndef","r");
365 if (file.isNil, { ^nil });
367 buffer = Int8Array.newClear(file.length);
372 this.sendMsg("/d_recv", buffer);
374 // tell server to load from disk
375 loadSynthDef { arg name, completionMsg, dir;
376 dir = dir ? SynthDef.synthDefDir;
378 ["/d_load", dir ++ name ++ ".scsyndef", completionMsg ]
382 loadDirectory { arg dir, completionMsg;
383 this.listSendMsg(["/d_loadDir", dir, completionMsg]);
386 serverRunning_ { arg val;
388 { this.changed(\bundling); }.defer;
390 if (val != serverRunning) {
391 if(thisProcess.platform.isSleeping.not) {
393 if (serverRunning.not) {
395 ServerQuit.run(this);
397 AppClock.sched(5.0, {
398 // still down after 5 seconds, assume server is really dead
399 // if you explicitly shut down the server then newAllocators
400 // and the \newAllocators notification will happen immediately
401 if(serverRunning.not) {
402 NotificationCenter.notify(this,\didQuit);
405 if(this.isLocal.not){
411 ServerBoot.run(this);
413 { this.changed(\serverRunning); }.defer;
419 wait { arg responseName;
421 routine = thisThread;
423 routine.resume(true);
424 }, responseName, addr).oneShot;
427 waitForBoot { arg onComplete, limit=100;
428 if(serverRunning.not) { this.boot };
429 this.doWhenBooted(onComplete, limit);
432 doWhenBooted { arg onComplete, limit=100;
433 var mBootNotifyFirst = bootNotifyFirst;
434 bootNotifyFirst = false;
439 or: (serverBooting and: mBootNotifyFirst.not))
440 and: {(limit = limit - 1) > 0})
441 and: { pid.tryPerform(\pidRunning) == true }
446 if(serverRunning.not,{
447 "server failed to start".error;
448 "For advice: [http://supercollider.sf.net/wiki/index.php/ERROR:_server_failed_to_start]".postln;
449 serverBooting = false;
454 bootSync { arg condition;
455 condition ?? { condition = Condition.new };
456 condition.test = false;
458 // Setting func to true indicates that our condition has become true and we can go when signaled.
459 condition.test = true;
465 ping { arg n=1, wait=0.1, func;
466 var result=0, pingFunc;
467 if(serverRunning.not) { "server not running".postln; ^this };
471 t = Main.elapsedTime;
473 dt = Main.elapsedTime - t;
474 ("measured latency:" + dt + "s").postln;
475 result = max(result, dt);
478 SystemClock.sched(wait, {pingFunc.value; nil })
480 ("maximum determined latency of" + name + ":" + result + "s").postln;
489 if(statusWatcher.isNil) {
495 this.sendNotifyRequest;
496 "Receiving notification messages from server %\n".postf(this.name);
500 #cmd, one, numUGens, numSynths, numGroups, numSynthDefs,
501 avgCPU, peakCPU, sampleRate, actualSampleRate = msg;
503 this.serverRunning_(true);
504 this.changed(\counts);
507 }, '/status.reply', addr).fix;
509 statusWatcher.enable;
512 // Buffer objects are cached in an Array for easy
513 // auto buffer info updating
515 this.deprecated(thisMethod, Buffer.findRespondingMethodFor(\cache));
521 this.deprecated(thisMethod, Buffer.findRespondingMethodFor(\uncache));
522 if((buf = Buffer.cachedBufferAt(this, i)).notNil) { buf.free };
525 // /b_info on the way
526 // keeps a reference count of waiting Buffers so that only one responder is needed
528 this.deprecated(thisMethod, Buffer.findRespondingMethodFor(\cache));
531 resetBufferAutoInfo {
532 this.deprecated(thisMethod, Meta_Buffer.findRespondingMethodFor(\clearServerCaches));
533 Buffer.clearServerCaches(this);
536 cachedBuffersDo { |func| Buffer.cachedBuffersDo(this, func) }
537 cachedBufferAt { |bufnum| ^Buffer.cachedBufferAt(this, bufnum) }
539 startAliveThread { arg delay=0.0;
540 this.addStatusWatcher;
542 aliveThread = Routine({
543 // this thread polls the server to see if it is alive
547 aliveThreadPeriod.wait;
548 this.serverRunning = alive;
552 AppClock.play(aliveThread);
557 if( aliveThread.notNil, {
561 if( statusWatcher.notNil, {
566 aliveThreadIsRunning {
567 ^aliveThread.notNil and: {aliveThread.isPlaying}
571 server.stopAliveThread;
572 server.startAliveThread(server.aliveThreadPeriod);
576 boot { arg startAliveThread=true, recover=false;
578 if (serverRunning, { "server already running".inform; ^this });
579 if (serverBooting, { "server already booting".inform; ^this });
581 serverBooting = true;
582 if(startAliveThread, { this.startAliveThread });
583 if(recover) { this.newNodeAllocators } { this.newAllocators };
584 bootNotifyFirst = true;
586 serverBooting = false;
587 if (sendQuit.isNil) {
588 sendQuit = not(this.inProcess) and: {this.isLocal};
591 (volume.volume != 0.0).if({
595 if (remoteControlled.not, {
596 "You will have to manually boot remote server.".inform;
604 "booting internal".inform;
607 //this.serverRunning = true;
608 pid = thisProcess.pid;
610 pid = (program ++ options.asOptionsString(addr.port)).unixCmd;
611 //unixCmd(program ++ options.asOptionsString(addr.port)).postln;
612 ("booting " ++ addr.port.asString).inform;
616 reboot { arg func; // func is evaluated when server is off
617 if (isLocal.not) { "can't reboot a remote server".inform; ^this };
636 notify_ { |flag = true|
640 this.sendNotifyRequest(true);
642 "Receiving notification messages from server %\n".postf(this.name);
645 this.sendNotifyRequest(false);
647 "Switched off notification messages from server %\n".postf(this.name);
651 sendNotifyRequest { arg flag=true;
653 addr.sendMsg("/notify", flag.binaryValue);
656 dumpOSC { arg code=1;
658 0 - turn dumping OFF.
659 1 - print the parsed contents of the message.
660 2 - print the contents in hexadecimal.
661 3 - print both the parsed and hexadecimal representations of the contents.
664 this.sendMsg(\dumpOSC,code);
668 var serverReallyQuitWatcher, serverReallyQuit = false;
670 statusWatcher.disable;
672 serverReallyQuitWatcher = OSCFunc({ |msg|
673 if(msg[1] == '/quit') {
674 statusWatcher.enable;
675 serverReallyQuit = true;
676 serverReallyQuitWatcher.free;
679 // don't accumulate quit-watchers if /done doesn't come back
680 AppClock.sched(3.0, {
681 if(serverReallyQuit.not) {
682 "Server % failed to quit after 3.0 seconds.".format(this.name).warn;
683 serverReallyQuitWatcher.free;
688 addr.sendMsg("/quit");
691 "quit done\n".inform;
693 "/quit sent\n".inform;
699 serverBooting = false;
701 this.serverRunning = false;
702 if(scopeWindow.notNil) { scopeWindow.quit };
703 RootNode(this).freeAll;
704 volume.isPlaying.if({
712 if (server.sendQuit === true) {
718 // if you see Exception in World_OpenUDP: unable to bind udp socket
719 // its because you have multiple servers running, left
720 // over from crashes, unexpected quits etc.
721 // you can't cause them to quit via OSC (the boot button)
723 // this brutally kills them all off
724 "killall -9 scsynth".unixCmd;
728 this.sendMsg("/g_freeAll", 0);
729 this.sendMsg("/clearSched");
733 *freeAll { arg evenRemote = false;
736 if ( server.serverRunning ) { server.freeAll }
740 if (server.isLocal and:{ server.serverRunning }) { server.freeAll }
745 *hardFreeAll { arg evenRemote = false;
752 if (server.isLocal) { server.freeAll }
758 ^this.all.select(_.serverRunning)
764 openBundle { arg bundle; // pass in a bundle that you want to
765 // continue adding to, or nil for a new bundle.
767 bundle = addr.bundle.addAll(bundle);
768 addr.bundle = []; // debatable
770 addr = BundleNetAddr.copyFrom(addr, bundle);
772 closeBundle { arg time; // set time to false if you don't want to send.
775 bundle = addr.closeBundle(time);
776 addr = addr.saveAddr;
778 "there is no open bundle.".warn
782 makeBundle { arg time, func, bundle;
783 this.openBundle(bundle);
786 bundle = this.closeBundle(time);
788 addr = addr.saveAddr; // on error restore the normal NetAddr
794 ^this.makeBundle(this.latency, func)
797 // internal server commands
799 ^options.bootInProcess;
803 ^this.primitiveFailed
805 allocSharedControls { arg numControls=1024;
807 ^this.primitiveFailed
809 setSharedControl { arg num, value;
811 ^this.primitiveFailed
813 getSharedControl { arg num;
815 ^this.primitiveFailed
821 this.prepareForRecord(path);
827 if(recordNode.isNil){
828 recordNode = Synth.tail(RootNode(this), "server-record",
829 [\bufnum, recordBuf.bufnum]);
833 "Recording: %\n".postf(recordBuf.path);
838 recordNode.notNil.if({ recordNode.run(false); "Paused".postln }, { "Not Recording".warn });
842 if(recordNode.notNil) {
845 recordBuf.close({ arg buf; buf.free; });
846 "Recording Stopped: %\n".postf(recordBuf.path);
853 prepareForRecord { arg path;
855 if(File.exists(thisProcess.platform.recordingsDir).not) {
856 systemCmd("mkdir" + thisProcess.platform.recordingsDir.quote);
859 // temporary kludge to fix Date's brokenness on windows
860 if(thisProcess.platform.name == \windows) {
861 path = thisProcess.platform.recordingsDir +/+ "SC_" ++ Main.elapsedTime.round(0.01) ++ "." ++ recHeaderFormat;
864 path = thisProcess.platform.recordingsDir +/+ "SC_" ++ Date.localtime.stamp ++ "." ++ recHeaderFormat;
867 recordBuf = Buffer.alloc(this, 65536, recChannels,
868 {arg buf; buf.writeMsg(path, recHeaderFormat, recSampleFormat, 0, 0, true);},
869 this.options.numBuffers + 1); // prevent buffer conflicts by using reserved bufnum
870 recordBuf.path = path;
871 SynthDef("server-record", { arg bufnum;
872 DiskOut.ar(bufnum, In.ar(0, recChannels))
878 // CmdPeriod support for Server-scope and Server-record and Server-volume
880 if(recordNode.notNil) { recordNode = nil; };
881 if(recordBuf.notNil) { recordBuf.close {|buf| buf.free; }; recordBuf = nil; };
883 this.changed(\cmdPeriod);
884 if(scopeWindow.notNil) {
885 fork { 0.5.wait; scopeWindow.run } // wait until synth is freed
887 CmdPeriod.remove(this)
891 defaultGroup { ^Group.basicNew(this, 1) }
893 queryAllNodes { arg queryControls = false;
894 var resp, done = false;
895 if(isLocal, {this.sendMsg("/g_dumpTree", 0, queryControls.binaryValue);}, {
896 resp = OSCFunc({ arg msg;
897 var i = 2, tabs = 0, printControls = false, dumpFunc;
898 if(msg[1] != 0, {printControls = true});
899 ("NODE TREE Group" + msg[2]).postln;
901 dumpFunc = {|numChildren|
905 if(msg[i + 1] >=0, {i = i + 2}, {
906 i = i + 3 + if(printControls, {msg[i + 3] * 2 + 1}, {0});
908 tabs.do({ " ".post });
909 msg[i].post; // nodeID
912 if(msg[i + 1] > 0, { dumpFunc.value(msg[i + 1]) });
914 (" " ++ msg[i + 2]).postln; // defname
918 tabs.do({ " ".post });
923 if(msg[i + 4 + j].isMemberOf(Symbol), {
924 (msg[i + 4 + j] ++ ": ").post;
935 dumpFunc.value(msg[3]);
938 }, '/g_queryTree.reply', addr).oneShot;
939 this.sendMsg("/g_queryTree", 0, queryControls.binaryValue);
940 SystemClock.sched(3, {
943 "Remote server failed to respond to queryAllNodes!".warn;
951 storeOn { arg stream;
952 var codeStr = this.switch (
953 Server.default, { if (sync_s) { "s" } { "Server.default" } },
954 Server.local, { "Server.local" },
955 Server.internal, { "Server.internal" },
956 { "Server.fromName(" + name.asCompileString + ")" }
961 archiveAsCompileString { ^true }
962 archiveAsObject { ^true }
964 volume_ {arg newVolume;
965 volume.volume_(newVolume);
976 reorder { arg nodeList, target, addAction=\addToHead;
977 target = target.asTarget;
978 this.sendMsg(62, Node.actionNumberFor(addAction), target.nodeID, *(nodeList.collect(_.nodeID))); //"/n_order"