1ServerOptions {
2	classvar defaultValues;
3
4	// order of variables is important here. Only add new instance variables to the end.
5	var <numAudioBusChannels;
6	var <>numControlBusChannels;
7	var <numInputBusChannels;
8	var <numOutputBusChannels;
9	var <>numBuffers;
10
11	var <>maxNodes;
12	var <>maxSynthDefs;
13	var <>protocol;
14	var <>blockSize;
15	var <>hardwareBufferSize;
16
17	var <>memSize;
18	var <>numRGens;
19	var <>numWireBufs;
20
21	var <>sampleRate;
22	var <>loadDefs;
23
24	var <>inputStreamsEnabled;
25	var <>outputStreamsEnabled;
26
27	var <>inDevice;
28	var <>outDevice;
29
30	var <>verbosity;
31	var <>zeroConf; // Whether server publishes port to Bonjour, etc.
32
33	var <>restrictedPath;
34	var <>ugenPluginsPath;
35
36	var <>initialNodeID;
37	var <>remoteControlVolume;
38
39	var <>memoryLocking;
40	var <>threads; // for supernova
41	var <>useSystemClock;  // for supernova
42
43	var <numPrivateAudioBusChannels;
44
45	var <>reservedNumAudioBusChannels;
46	var <>reservedNumControlBusChannels;
47	var <>reservedNumBuffers;
48	var <>pingsBeforeConsideredDead;
49
50	var <>maxLogins;
51
52	var <>recHeaderFormat;
53	var <>recSampleFormat;
54	var <>recChannels;
55	var <>recBufSize;
56
57	var <>bindAddress;
58
59	*initClass {
60		defaultValues = IdentityDictionary.newFrom(
61			(
62				numAudioBusChannels: 1024, // see corresponding setter method below
63				numControlBusChannels: 16384,
64				numInputBusChannels: 2, // see corresponding setter method below
65				numOutputBusChannels: 2, // see corresponding setter method below
66				numBuffers: 1024,
67				maxNodes: 1024,
68				maxSynthDefs: 1024,
69				protocol: \udp,
70				blockSize: 64,
71				hardwareBufferSize: nil,
72				memSize: 8192,
73				numRGens: 64,
74				numWireBufs: 64,
75				sampleRate: nil,
76				loadDefs: true,
77				inputStreamsEnabled: nil,
78				outputStreamsEnabled: nil,
79				inDevice: nil,
80				outDevice: nil,
81				verbosity: 0,
82				zeroConf: false,
83				restrictedPath: nil,
84				ugenPluginsPath: nil,
85				initialNodeID: 1000,
86				remoteControlVolume: false,
87				memoryLocking: false,
88				threads: nil,
89				useSystemClock: false,
90				numPrivateAudioBusChannels: 1020, // see corresponding setter method below
91				reservedNumAudioBusChannels: 0,
92				reservedNumControlBusChannels: 0,
93				reservedNumBuffers: 0,
94				pingsBeforeConsideredDead: 5,
95				maxLogins: 1,
96				recHeaderFormat: "aiff",
97				recSampleFormat: "float",
98				recChannels: 2,
99				recBufSize: nil,
100				bindAddress: "127.0.0.1",
101			)
102		)
103	}
104
105	*new {
106		^super.new.init
107	}
108
109	init {
110		defaultValues.keysValuesDo { |key, val| this.instVarPut(key, val) }
111	}
112
113	device {
114		^if(inDevice == outDevice) {
115			inDevice
116		} {
117			[inDevice, outDevice]
118		}
119	}
120
121	device_ { |dev|
122		inDevice = outDevice = dev;
123	}
124
125	asOptionsString { | port = 57110 |
126		var o;
127		o = if(protocol == \tcp, " -t ", " -u ");
128		o = o ++ port;
129
130		o = o ++ " -a " ++ (numPrivateAudioBusChannels + numInputBusChannels + numOutputBusChannels) ;
131		o = o ++ " -i " ++ numInputBusChannels;
132		o = o ++ " -o " ++ numOutputBusChannels;
133
134		if (bindAddress != defaultValues[\bindAddress], {
135			o = o ++ " -B " ++ bindAddress;
136		});
137		if (numControlBusChannels !== defaultValues[\numControlBusChannels], {
138			numControlBusChannels = numControlBusChannels.asInteger;
139			o = o ++ " -c " ++ numControlBusChannels;
140		});
141		if (numBuffers !== defaultValues[\numBuffers], {
142			numBuffers = numBuffers.asInteger;
143			o = o ++ " -b " ++ numBuffers;
144		});
145		if (maxNodes !== defaultValues[\maxNodes], {
146			maxNodes = maxNodes.asInteger;
147			o = o ++ " -n " ++ maxNodes;
148		});
149		if (maxSynthDefs !== defaultValues[\maxSynthDefs], {
150			maxSynthDefs = maxSynthDefs.asInteger;
151			o = o ++ " -d " ++ maxSynthDefs;
152		});
153		if (blockSize !== defaultValues[\blockSize], {
154			blockSize = blockSize.asInteger;
155			o = o ++ " -z " ++ blockSize;
156		});
157		if (hardwareBufferSize.notNil, {
158			o = o ++ " -Z " ++ hardwareBufferSize;
159		});
160		if (memSize !== defaultValues[\memSize], {
161			memSize = memSize.asInteger;
162			o = o ++ " -m " ++ memSize;
163		});
164		if (numRGens !== defaultValues[\numRGens], {
165			numRGens = numRGens.asInteger;
166			o = o ++ " -r " ++ numRGens;
167		});
168		if (numWireBufs !== defaultValues[\numWireBufs], {
169			numWireBufs = numWireBufs.asInteger;
170			o = o ++ " -w " ++ numWireBufs;
171		});
172		if (sampleRate.notNil, {
173			o = o ++ " -S " ++ sampleRate;
174		});
175		if (loadDefs.not, {
176			o = o ++ " -D 0";
177		});
178		if (inputStreamsEnabled.notNil, {
179			o = o ++ " -I " ++ inputStreamsEnabled ;
180		});
181		if (outputStreamsEnabled.notNil, {
182			o = o ++ " -O " ++ outputStreamsEnabled ;
183		});
184		if (inDevice == outDevice)
185		{
186			if (inDevice.notNil,
187				{
188					o = o ++ " -H %".format(inDevice.quote);
189			});
190		}
191		{
192			o = o ++ " -H % %".format(inDevice.asString.quote, outDevice.asString.quote);
193		};
194		if (verbosity != defaultValues[\verbosity], {
195			o = o ++ " -V " ++ verbosity;
196		});
197		if (zeroConf.not, {
198			o = o ++ " -R 0";
199		});
200		if (restrictedPath.notNil, {
201			o = o ++ " -P " ++ restrictedPath;
202		});
203		if (ugenPluginsPath.notNil, {
204			if(ugenPluginsPath.isString, {
205				ugenPluginsPath = ugenPluginsPath.bubble;
206			});
207			o = o ++ " -U " ++ ugenPluginsPath.collect{|p|
208				thisProcess.platform.formatPathForCmdLine(p)
209			}.join(Platform.pathDelimiter);
210		});
211		if (memoryLocking, {
212			o = o ++ " -L";
213		});
214		if (threads.notNil, {
215			if (Server.program.asString.endsWith("supernova")) {
216				o = o ++ " -T " ++ threads;
217			}
218		});
219		if (useSystemClock, {
220			o = o ++ " -C 1"
221		});
222		if (maxLogins.notNil, {
223			o = o ++ " -l " ++ maxLogins;
224		});
225		^o
226	}
227
228	firstPrivateBus { // after the outs and ins
229		^numOutputBusChannels + numInputBusChannels
230	}
231
232	bootInProcess {
233		_BootInProcessServer
234		^this.primitiveFailed
235	}
236
237	numPrivateAudioBusChannels_ { |numChannels = 1020| // arg default value should match defaultValues above
238		numPrivateAudioBusChannels = numChannels;
239		this.recalcChannels;
240	}
241
242	numAudioBusChannels_ { |numChannels = 1024| // arg default value should match defaultValues above
243		numAudioBusChannels = numChannels;
244		numPrivateAudioBusChannels = numAudioBusChannels - numInputBusChannels - numOutputBusChannels;
245	}
246
247	numInputBusChannels_ { |numChannels = 2| // arg default value should match defaultValues above
248		numInputBusChannels = numChannels;
249		this.recalcChannels;
250	}
251
252	numOutputBusChannels_ { |numChannels = 2| // arg default value should match defaultValues above
253		numOutputBusChannels = numChannels;
254		this.recalcChannels;
255	}
256
257	recalcChannels {
258		numAudioBusChannels = numPrivateAudioBusChannels + numInputBusChannels + numOutputBusChannels;
259	}
260
261	*prListDevices {
262		arg in, out;
263		_ListAudioDevices
264		^this.primitiveFailed
265	}
266
267	*devices {
268		^this.prListDevices(1, 1);
269	}
270
271	*inDevices {
272		^this.prListDevices(1, 0);
273	}
274
275	*outDevices {
276		^this.prListDevices(0, 1);
277	}
278}
279
280
281ServerShmInterface {
282	// order matters!
283	var ptr, finalizer;
284
285	*new {|port|
286		^super.new.connect(port)
287	}
288
289	copy {
290		// never ever copy! will cause duplicate calls to the finalizer!
291		^this
292	}
293
294	connect {
295		_ServerShmInterface_connectSharedMem
296		^this.primitiveFailed
297	}
298
299	disconnect {
300		_ServerShmInterface_disconnectSharedMem
301		^this.primitiveFailed
302	}
303
304	getControlBusValue {
305		_ServerShmInterface_getControlBusValue
306		^this.primitiveFailed
307	}
308
309	getControlBusValues {
310		_ServerShmInterface_getControlBusValues
311		^this.primitiveFailed
312	}
313
314	setControlBusValue {
315		_ServerShmInterface_setControlBusValue
316		^this.primitiveFailed
317	}
318
319	setControlBusValues {
320		_ServerShmInterface_setControlBusValues
321		^this.primitiveFailed
322	}
323}
324
325
326Server {
327
328	classvar <>local, <>internal, <default;
329	classvar <>named, <>all, <>program, <>sync_s = true;
330	classvar <>nodeAllocClass, <>bufferAllocClass, <>busAllocClass;
331
332	var <name, <addr, <clientID;
333	var <isLocal, <inProcess, <>sendQuit, <>remoteControlled;
334	var maxNumClients; // maxLogins as sent from booted scsynth
335
336	var <>options, <>latency = 0.2, <dumpMode = 0;
337
338	var <nodeAllocator, <controlBusAllocator, <audioBusAllocator, <bufferAllocator, <scopeBufferAllocator;
339
340	var <>tree;
341	var <defaultGroup, <defaultGroups;
342
343	var <syncThread, <syncTasks;
344	var <window, <>scopeWindow, <emacsbuf;
345	var <volume, <recorder, <statusWatcher;
346	var <pid, serverInterface;
347	var pidReleaseCondition;
348
349	*initClass {
350		Class.initClassTree(ServerOptions);
351		Class.initClassTree(NotificationCenter);
352		named = IdentityDictionary.new;
353		all = Set.new;
354
355		nodeAllocClass = NodeIDAllocator;
356		// nodeAllocClass = ReadableNodeIDAllocator;
357		bufferAllocClass = ContiguousBlockAllocator;
358		busAllocClass = ContiguousBlockAllocator;
359
360		default = local = Server.new(\localhost, NetAddr("127.0.0.1", 57110));
361		internal = Server.new(\internal, NetAddr.new);
362	}
363
364	*fromName { |name|
365		^Server.named[name] ?? {
366			Server(name, NetAddr.new("127.0.0.1", 57110), ServerOptions.new)
367		}
368	}
369
370	*default_ { |server|
371		default = server;
372		if(sync_s) { thisProcess.interpreter.s = server };  // sync with s?
373		this.all.do { |each| each.changed(\default, server) };
374	}
375
376	*new { |name, addr, options, clientID|
377		^super.new.init(name, addr, options, clientID)
378	}
379
380	*remote { |name, addr, options, clientID|
381		var result;
382		result = this.new(name, addr, options, clientID);
383		result.startAliveThread;
384		^result;
385	}
386
387	init { |argName, argAddr, argOptions, argClientID|
388		this.addr = argAddr;
389		options = argOptions ?? { ServerOptions.new };
390
391		// set name to get readable posts from clientID set
392		name = argName.asSymbol;
393
394		// make statusWatcher before clientID, so .serverRunning works
395		statusWatcher = ServerStatusWatcher(server: this);
396
397		// go thru setter to test validity
398		this.clientID = argClientID ? 0;
399
400		volume = Volume(server: this, persist: true);
401		recorder = Recorder(server: this);
402		recorder.notifyServer = true;
403
404		this.name = argName;
405		all.add(this);
406
407		pidReleaseCondition = Condition({ this.pid == nil });
408
409		Server.changed(\serverAdded, this);
410
411	}
412
413	maxNumClients { ^maxNumClients ?? { options.maxLogins } }
414
415	remove {
416		all.remove(this);
417		named.removeAt(this.name);
418	}
419
420	addr_ { |netAddr|
421		addr = netAddr ?? { NetAddr("127.0.0.1", 57110) };
422		inProcess = addr.addr == 0;
423		isLocal = inProcess || { addr.isLocal };
424		remoteControlled = isLocal.not;
425	}
426
427	name_ { |argName|
428		name = argName.asSymbol;
429		if(named.at(argName).notNil) {
430			"Server name already exists: '%'. Please use a unique name".format(name, argName).warn;
431		} {
432			named.put(name, this);
433		}
434	}
435
436	initTree {
437		fork({
438			this.sendDefaultGroups;
439			tree.value(this);
440			this.sync;
441			ServerTree.run(this);
442			this.sync;
443		}, AppClock);
444	}
445
446	/* clientID */
447
448	// clientID is settable while server is off, and locked while server is running
449	// called from prHandleClientLoginInfoFromServer once after booting.
450	clientID_ { |val|
451		var failstr = "Server % couldn't set clientID to % - %. clientID is still %.";
452		if (this.serverRunning) {
453			failstr.format(name, val.cs, "server is running", clientID).warn;
454			^this
455		};
456
457		if(val.isInteger.not) {
458			failstr.format(name, val.cs, "not an Integer", clientID).warn;
459			^this
460		};
461		if (val < 0 or: { val >= this.maxNumClients }) {
462			failstr.format(name,
463				val.cs,
464				"outside of allowed server.maxNumClients range of 0 - %".format(this.maxNumClients - 1),
465				clientID
466			).warn;
467			^this
468		};
469
470		if (clientID != val) {
471			"% : setting clientID to %.\n".postf(this, val);
472		};
473		clientID = val;
474		this.newAllocators;
475	}
476
477	/* clientID-based id allocators */
478
479	newAllocators {
480		this.newNodeAllocators;
481		this.newBusAllocators;
482		this.newBufferAllocators;
483		this.newScopeBufferAllocators;
484		NotificationCenter.notify(this, \newAllocators);
485	}
486
487	newNodeAllocators {
488		nodeAllocator = nodeAllocClass.new(
489			clientID,
490			options.initialNodeID,
491			this.maxNumClients
492		);
493		// defaultGroup and defaultGroups depend on allocator,
494		// so always make them here:
495		this.makeDefaultGroups;
496	}
497
498	newBusAllocators {
499		var numControlPerClient, numAudioPerClient;
500		var controlReservedOffset, controlBusClientOffset;
501		var audioReservedOffset, audioBusClientOffset;
502
503		var audioBusIOOffset = options.firstPrivateBus;
504
505		numControlPerClient = options.numControlBusChannels div: this.maxNumClients;
506		numAudioPerClient = options.numAudioBusChannels - audioBusIOOffset div: this.maxNumClients;
507
508		controlReservedOffset = options.reservedNumControlBusChannels;
509		controlBusClientOffset = numControlPerClient * clientID;
510
511		audioReservedOffset = options.reservedNumAudioBusChannels;
512		audioBusClientOffset = numAudioPerClient * clientID;
513
514		controlBusAllocator = busAllocClass.new(
515			numControlPerClient,
516			controlReservedOffset,
517			controlBusClientOffset
518		);
519		audioBusAllocator = busAllocClass.new(
520			numAudioPerClient,
521			audioReservedOffset,
522			audioBusClientOffset + audioBusIOOffset
523		);
524	}
525
526	newBufferAllocators {
527		var numBuffersPerClient = options.numBuffers div: this.maxNumClients;
528		var numReservedBuffers = options.reservedNumBuffers;
529		var bufferClientOffset = numBuffersPerClient * clientID;
530
531		bufferAllocator = bufferAllocClass.new(
532			numBuffersPerClient,
533			numReservedBuffers,
534			bufferClientOffset
535		);
536	}
537
538	newScopeBufferAllocators {
539		if(isLocal) {
540			scopeBufferAllocator = StackNumberAllocator.new(0, 127)
541		}
542	}
543
544	nextBufferNumber { |n|
545		var bufnum = bufferAllocator.alloc(n);
546		if(bufnum.isNil) {
547			if(n > 1) {
548				Error("No block of % consecutive buffer numbers is available.".format(n)).throw
549			} {
550				Error("No more buffer numbers -- free some buffers before allocating more.").throw
551			}
552		};
553		^bufnum
554	}
555
556	freeAllBuffers {
557		var bundle;
558		bufferAllocator.blocks.do { arg block;
559			(block.address .. block.address + block.size - 1).do { |i|
560				bundle = bundle.add( ["/b_free", i] );
561			};
562			bufferAllocator.free(block.address);
563		};
564		this.sendBundle(nil, *bundle);
565	}
566
567	nextNodeID {
568		^nodeAllocator.alloc
569	}
570
571	nextPermNodeID {
572		^nodeAllocator.allocPerm
573	}
574
575	freePermNodeID { |id|
576		^nodeAllocator.freePerm(id)
577	}
578
579	prHandleClientLoginInfoFromServer { |newClientID, newMaxLogins|
580
581		// only set maxLogins if not internal server
582		if (inProcess.not) {
583			if (newMaxLogins.notNil) {
584				if (newMaxLogins != options.maxLogins) {
585					"%: server process has maxLogins % - adjusting my options accordingly.\n"
586					.postf(this, newMaxLogins);
587				} {
588					"%: server process's maxLogins (%) matches with my options.\n"
589					.postf(this, newMaxLogins);
590				};
591				options.maxLogins = maxNumClients = newMaxLogins;
592			} {
593				"%: no maxLogins info from server process.\n"
594				.postf(this, newMaxLogins);
595			};
596		};
597		if (newClientID.notNil) {
598			if (newClientID == clientID) {
599				"%: keeping clientID (%) as confirmed by server process.\n"
600				.postf(this, newClientID);
601			} {
602				"%: setting clientID to %, as obtained from server process.\n"
603				.postf(this, newClientID);
604			};
605			this.clientID = newClientID;
606		};
607	}
608
609	prHandleNotifyFailString {|failString, msg|
610
611		// post info on some known error cases
612		case
613		{ failString.asString.contains("already registered") } {
614			// when already registered, msg[3] is the clientID by which
615			// the requesting client was registered previously
616			"% - already registered with clientID %.\n".postf(this, msg[3]);
617			statusWatcher.prHandleLoginWhenAlreadyRegistered(msg[3]);
618
619		} { failString.asString.contains("not registered") } {
620			// unregister when already not registered:
621			"% - not registered.\n".postf(this);
622			statusWatcher.notified = false;
623		} { failString.asString.contains("too many users") } {
624			"% - could not register, too many users.\n".postf(this);
625			statusWatcher.notified = false;
626		} {
627			// throw error if unknown failure
628			Error(
629				"Failed to register with server '%' for notifications: %\n"
630				"To recover, please reboot the server.".format(this, msg)).throw;
631		};
632	}
633
634	/* network messages */
635
636	sendMsg { |... msg|
637		addr.sendMsg(*msg)
638	}
639
640	sendBundle { |time ... msgs|
641		addr.sendBundle(time, *msgs)
642	}
643
644	sendRaw { |rawArray|
645		addr.sendRaw(rawArray)
646	}
647
648	sendMsgSync { |condition ... args|
649		var cmdName, resp;
650		if(condition.isNil) { condition = Condition.new };
651		cmdName = args[0].asString;
652		if(cmdName[0] != $/) { cmdName = cmdName.insert(0, $/) };
653		resp = OSCFunc({|msg|
654			if(msg[1].asString == cmdName) {
655				resp.free;
656				condition.test = true;
657				condition.signal;
658			};
659		}, '/done', addr);
660		condition.test = false;
661		addr.sendBundle(nil, args);
662		condition.wait;
663	}
664
665	sync { |condition, bundles, latency| // array of bundles that cause async action
666		addr.sync(condition, bundles, latency)
667	}
668
669	schedSync { |func|
670		syncTasks = syncTasks.add(func);
671		if(syncThread.isNil) {
672			syncThread = Routine.run {
673				var c = Condition.new;
674				while { syncTasks.notEmpty } { syncTasks.removeAt(0).value(c) };
675				syncThread = nil;
676			}
677		}
678	}
679
680	listSendMsg { |msg|
681		addr.sendMsg(*msg)
682	}
683
684	listSendBundle { |time, msgs|
685		addr.sendBundle(time, *(msgs.asArray))
686	}
687
688	reorder { |nodeList, target, addAction=\addToHead|
689		target = target.asTarget;
690		this.sendMsg(62, Node.actionNumberFor(addAction), target.nodeID, *(nodeList.collect(_.nodeID))) //"/n_order"
691	}
692
693	// load from disk locally, send remote
694	sendSynthDef { |name, dir|
695		var file, buffer;
696		dir = dir ? SynthDef.synthDefDir;
697		file = File(dir ++ name ++ ".scsyndef","r");
698		if(file.isOpen.not) { ^nil };
699		protect {
700			buffer = Int8Array.newClear(file.length);
701			file.read(buffer);
702		} {
703			file.close;
704		};
705		this.sendMsg("/d_recv", buffer);
706	}
707
708	// tell server to load from disk
709	loadSynthDef { |name, completionMsg, dir|
710		dir = dir ? SynthDef.synthDefDir;
711		this.listSendMsg(
712			["/d_load", dir ++ name ++ ".scsyndef", completionMsg ]
713		)
714	}
715
716	//loadDir
717	loadDirectory { |dir, completionMsg|
718		this.listSendMsg(["/d_loadDir", dir, completionMsg])
719	}
720
721
722	/* network message bundling */
723
724	openBundle { |bundle|	// pass in a bundle that you want to
725		// continue adding to, or nil for a new bundle.
726		if(addr.hasBundle) {
727			bundle = addr.bundle.addAll(bundle);
728			addr.bundle = []; // debatable
729		};
730		addr = BundleNetAddr.copyFrom(addr, bundle);
731	}
732
733	closeBundle { |time| // set time to false if you don't want to send.
734		var bundle;
735		if(addr.hasBundle) {
736			bundle = addr.closeBundle(time);
737			addr = addr.saveAddr;
738		} {
739			"there is no open bundle.".warn
740		};
741		^bundle;
742	}
743
744	makeBundle { |time, func, bundle|
745		this.openBundle(bundle);
746		try {
747			func.value(this);
748			bundle = this.closeBundle(time);
749		} {|error|
750			addr = addr.saveAddr; // on error restore the normal NetAddr
751			error.throw
752		}
753		^bundle
754	}
755
756	bind { |func|
757		^this.makeBundle(this.latency, func)
758	}
759
760
761	/* scheduling */
762
763	wait { |responseName|
764		var routine = thisThread;
765		OSCFunc({
766			routine.resume(true)
767		}, responseName, addr).oneShot;
768	}
769
770	waitForBoot { |onComplete, limit = 100, onFailure|
771		// onFailure.true: why is this necessary?
772		// this.boot also calls doWhenBooted.
773		// doWhenBooted prints the normal boot failure message.
774		// if the server fails to boot, the failure error gets posted TWICE.
775		// So, we suppress one of them.
776		if(this.serverRunning.not) { this.boot(onFailure: true) };
777		this.doWhenBooted(onComplete, limit, onFailure);
778	}
779
780	doWhenBooted { |onComplete, limit=100, onFailure|
781		statusWatcher.doWhenBooted(onComplete, limit, onFailure)
782	}
783
784	ifRunning { |func, failFunc|
785		^if(statusWatcher.unresponsive) {
786			"server '%' not responsive".format(this.name).postln;
787			failFunc.value(this)
788		} {
789			if(statusWatcher.serverRunning) {
790				func.value(this)
791			} {
792				"server '%' not running".format(this.name).postln;
793				failFunc.value(this)
794			}
795		}
796
797	}
798
799	ifNotRunning { |func|
800		^ifRunning(this, nil, func)
801	}
802
803
804	bootSync { |condition|
805		condition ?? { condition = Condition.new };
806		condition.test = false;
807		this.waitForBoot {
808			// Setting func to true indicates that our condition has become true and we can go when signaled.
809			condition.test = true;
810			condition.signal
811		};
812		condition.wait;
813	}
814
815
816	ping { |n = 1, wait = 0.1, func|
817		var result = 0, pingFunc;
818		if(statusWatcher.serverRunning.not) { "server not running".postln; ^this };
819		pingFunc = {
820			Routine.run {
821				var t, dt;
822				t = Main.elapsedTime;
823				this.sync;
824				dt = Main.elapsedTime - t;
825				("measured latency:" + dt + "s").postln;
826				result = max(result, dt);
827				n = n - 1;
828				if(n > 0) {
829					SystemClock.sched(wait, { pingFunc.value; nil })
830				} {
831					("maximum determined latency of" + name + ":" + result + "s").postln;
832					func.value(result)
833				}
834			};
835		};
836		pingFunc.value;
837	}
838
839	cachedBuffersDo { |func|
840		Buffer.cachedBuffersDo(this, func)
841	}
842
843	cachedBufferAt { |bufnum|
844		^Buffer.cachedBufferAt(this, bufnum)
845	}
846
847	// keep defaultGroups for all clients on this server:
848	makeDefaultGroups {
849		defaultGroups = this.maxNumClients.collect { |clientID|
850			Group.basicNew(this, nodeAllocator.numIDs * clientID + 1);
851		};
852		defaultGroup = defaultGroups[clientID];
853	}
854
855	defaultGroupID { ^defaultGroup.nodeID }
856
857	sendDefaultGroups {
858		defaultGroups.do { |group|
859			this.sendMsg("/g_new", group.nodeID, 0, 0);
860		};
861	}
862
863	sendDefaultGroupsForClientIDs { |clientIDs|
864		defaultGroups[clientIDs].do { |group|
865			this.sendMsg("/g_new", group.nodeID, 0, 0);
866		}
867	}
868
869	inputBus {
870		^Bus(\audio, this.options.numOutputBusChannels, this.options.numInputBusChannels, this)
871	}
872
873	outputBus {
874		^Bus(\audio, 0, this.options.numOutputBusChannels, this)
875	}
876
877	/* recording formats */
878
879	recHeaderFormat { ^options.recHeaderFormat }
880	recHeaderFormat_ { |string| options.recHeaderFormat_(string) }
881	recSampleFormat { ^options.recSampleFormat }
882	recSampleFormat_ { |string| options.recSampleFormat_(string) }
883	recChannels { ^options.recChannels }
884	recChannels_ { |n| options.recChannels_(n) }
885	recBufSize { ^options.recBufSize }
886	recBufSize_ { |n| options.recBufSize_(n) }
887
888	/* server status */
889
890	numUGens { ^statusWatcher.numUGens }
891	numSynths { ^statusWatcher.numSynths }
892	numGroups { ^statusWatcher.numGroups }
893	numSynthDefs { ^statusWatcher.numSynthDefs }
894	avgCPU { ^statusWatcher.avgCPU }
895	peakCPU { ^statusWatcher.peakCPU }
896	sampleRate { ^statusWatcher.sampleRate }
897	actualSampleRate { ^statusWatcher.actualSampleRate }
898	hasBooted { ^statusWatcher.hasBooted }
899	serverRunning { ^statusWatcher.serverRunning }
900	serverBooting { ^statusWatcher.serverBooting }
901	unresponsive { ^statusWatcher.unresponsive }
902	startAliveThread { | delay=0.0 | statusWatcher.startAliveThread(delay) }
903	stopAliveThread { statusWatcher.stopAliveThread }
904	aliveThreadIsRunning { ^statusWatcher.aliveThread.isPlaying }
905	aliveThreadPeriod_ { |val| statusWatcher.aliveThreadPeriod_(val) }
906	aliveThreadPeriod { |val| ^statusWatcher.aliveThreadPeriod }
907
908	disconnectSharedMemory {
909		if(this.hasShmInterface) {
910			"server '%' disconnected shared memory interface\n".postf(name);
911			serverInterface.disconnect;
912			serverInterface = nil;
913		}
914	}
915
916	connectSharedMemory {
917		var id;
918		if(this.isLocal) {
919			this.disconnectSharedMemory;
920			id = if(this.inProcess) { thisProcess.pid } { addr.port };
921			serverInterface = ServerShmInterface(id);
922		}
923	}
924
925	hasShmInterface { ^serverInterface.notNil }
926
927	*resumeThreads {
928		all.do { |server| server.statusWatcher.resumeThread }
929	}
930
931	boot { | startAliveThread = true, recover = false, onFailure |
932
933		if(statusWatcher.unresponsive) {
934			"server '%' unresponsive, rebooting ...".format(this.name).postln;
935			this.quit(watchShutDown: false)
936		};
937		if(statusWatcher.serverRunning) { "server '%' already running".format(this.name).postln; ^this };
938		if(statusWatcher.serverBooting) { "server '%' already booting".format(this.name).postln; ^this };
939
940
941		statusWatcher.serverBooting = true;
942
943		statusWatcher.doWhenBooted({
944			statusWatcher.serverBooting = false;
945			this.bootInit(recover);
946		}, onFailure: onFailure ? false);
947
948		if(remoteControlled) {
949			"You will have to manually boot remote server.".postln;
950		} {
951			this.prPingApp({
952				this.quit;
953				this.boot;
954			}, {
955				this.prWaitForPidRelease {
956					this.bootServerApp({
957						if(startAliveThread) { statusWatcher.startAliveThread }
958					})
959				};
960			}, 0.25);
961		}
962	}
963
964	prWaitForPidRelease { |onComplete, onFailure, timeout = 1|
965		var waiting = true;
966		if (this.inProcess or: { this.isLocal.not or: { this.pid.isNil } }) {
967			onComplete.value;
968			^this
969		};
970
971		// FIXME: quick and dirty fix for supernova reboot hang on macOS:
972		// if we have just quit before running server.boot,
973		// we wait until server process really ends and sets its pid to nil
974		SystemClock.sched(timeout, {
975			if (waiting) {
976				pidReleaseCondition.unhang
977			}
978		});
979
980		forkIfNeeded {
981			pidReleaseCondition.hang;
982			if (pidReleaseCondition.test.value) {
983				waiting = false;
984				onComplete.value;
985			} {
986				onFailure.value
987			}
988		}
989	}
990
991	// FIXME: recover should happen later, after we have a valid clientID!
992	// would then need check whether maxLogins and clientID have changed or not,
993	// and recover would only be possible if no changes.
994	bootInit { | recover = false |
995		// if(recover) { this.newNodeAllocators } {
996		// 	"% calls newAllocators\n".postf(thisMethod);
997		// this.newAllocators };
998		if(dumpMode != 0) { this.sendMsg(\dumpOSC, dumpMode) };
999		if(sendQuit.isNil) {
1000			sendQuit = this.inProcess or: { this.isLocal };
1001		};
1002		this.connectSharedMemory;
1003	}
1004
1005	prOnServerProcessExit { |exitCode|
1006		pid = nil;
1007		pidReleaseCondition.signal;
1008
1009		"Server '%' exited with exit code %."
1010			.format(this.name, exitCode)
1011			.postln;
1012		statusWatcher.quit(watchShutDown: false);
1013	}
1014
1015	bootServerApp { |onComplete|
1016		if(inProcess) {
1017			"Booting internal server.".postln;
1018			this.bootInProcess;
1019			pid = thisProcess.pid;
1020			onComplete.value;
1021		} {
1022			this.disconnectSharedMemory;
1023			pid = unixCmd(program ++ options.asOptionsString(addr.port), { |exitCode|
1024				this.prOnServerProcessExit(exitCode);
1025			});
1026			("Booting server '%' on address %:%.").format(this.name, addr.hostname, addr.port.asString).postln;
1027			// in case the server takes more time to boot
1028			// we increase the number of attempts for tcp connection
1029			// in order to minimize the chance of timing out
1030			if(options.protocol == \tcp, { addr.tryConnectTCP(onComplete, nil, 20) }, onComplete);
1031		}
1032	}
1033
1034	reboot { |func, onFailure| // func is evaluated when server is off
1035		if(isLocal.not) { "can't reboot a remote server".postln; ^this };
1036		if(statusWatcher.serverRunning and: { this.unresponsive.not }) {
1037			this.quit({
1038				func.value;
1039				defer { this.boot }
1040			}, onFailure);
1041		} {
1042			func.value;
1043			this.boot(onFailure: onFailure);
1044		}
1045	}
1046
1047	applicationRunning {
1048		^pid.tryPerform(\pidRunning) == true
1049	}
1050
1051	status {
1052		addr.sendStatusMsg // backward compatibility
1053	}
1054
1055	sendStatusMsg {
1056		addr.sendStatusMsg
1057	}
1058
1059	notify {
1060		^statusWatcher.notify
1061	}
1062
1063	notify_ { |flag|
1064		statusWatcher.notify_(flag)
1065	}
1066
1067	notified {
1068		^statusWatcher.notified
1069	}
1070
1071	dumpOSC { |code = 1|
1072		/*
1073		0 - turn dumping OFF.
1074		1 - print the parsed contents of the message.
1075		2 - print the contents in hexadecimal.
1076		3 - print both the parsed and hexadecimal representations of the contents.
1077		*/
1078		dumpMode = code;
1079		this.sendMsg(\dumpOSC, code);
1080		this.changed(\dumpOSC, code);
1081	}
1082
1083	quit { |onComplete, onFailure, watchShutDown = true|
1084		var func;
1085
1086		addr.sendMsg("/quit");
1087
1088		if(watchShutDown and: { this.unresponsive }) {
1089			"Server '%' was unresponsive. Quitting anyway.".format(name).postln;
1090			watchShutDown = false;
1091		};
1092
1093		if(options.protocol == \tcp) {
1094			statusWatcher.quit({ addr.tryDisconnectTCP(onComplete, onFailure) }, nil, watchShutDown);
1095		} {
1096			statusWatcher.quit(onComplete, onFailure, watchShutDown);
1097		};
1098
1099		if(inProcess) {
1100			this.quitInProcess;
1101			"Internal server has quit.".postln;
1102		} {
1103			"'/quit' message sent to server '%'.".format(name).postln;
1104		};
1105
1106		// let server process reset pid to nil!
1107		// pid = nil;
1108		sendQuit = nil;
1109		maxNumClients = nil;
1110
1111		if(scopeWindow.notNil) { scopeWindow.quit };
1112		volume.freeSynth;
1113		RootNode(this).freeAll;
1114		this.newAllocators;
1115	}
1116
1117	*quitAll { |watchShutDown = true|
1118		all.do { |server|
1119			if(server.sendQuit === true) {
1120				server.quit(watchShutDown: watchShutDown)
1121			}
1122		}
1123	}
1124
1125	*killAll {
1126		// if you see Exception in World_OpenUDP: unable to bind udp socket
1127		// its because you have multiple servers running, left
1128		// over from crashes, unexpected quits etc.
1129		// you can't cause them to quit via OSC (the boot button)
1130
1131		// this brutally kills them all off
1132		thisProcess.platform.killAll(this.program.basename);
1133		this.quitAll(watchShutDown: false);
1134	}
1135
1136	freeAll {
1137		this.sendMsg("/g_freeAll", 0);
1138		this.sendMsg("/clearSched");
1139		this.initTree;
1140	}
1141
1142	freeMyDefaultGroup {
1143		this.sendMsg("/g_freeAll", defaultGroup.nodeID);
1144	}
1145
1146	freeDefaultGroups {
1147		defaultGroups.do { |group|
1148			this.sendMsg("/g_freeAll", group.nodeID);
1149		};
1150	}
1151
1152	*freeAll { |evenRemote = false|
1153		if(evenRemote) {
1154			all.do { |server|
1155				if( server.serverRunning ) { server.freeAll }
1156			}
1157		} {
1158			all.do { |server|
1159				if(server.isLocal and:{ server.serverRunning }) { server.freeAll }
1160			}
1161		}
1162	}
1163
1164	*hardFreeAll { |evenRemote = false|
1165		if(evenRemote) {
1166			all.do { |server|
1167				server.freeAll
1168			}
1169		} {
1170			all.do { |server|
1171				if(server.isLocal) { server.freeAll }
1172			}
1173		}
1174	}
1175
1176	*allBootedServers {
1177		^this.all.select(_.hasBooted)
1178	}
1179
1180	*allRunningServers {
1181		^this.all.select(_.serverRunning)
1182	}
1183
1184	/* volume control */
1185
1186	volume_ { | newVolume |
1187		volume.volume_(newVolume)
1188	}
1189
1190	mute {
1191		volume.mute
1192	}
1193
1194	unmute {
1195		volume.unmute
1196	}
1197
1198	/* recording output */
1199
1200	record { |path, bus, numChannels, node, duration| recorder.record(path, bus, numChannels, node, duration) }
1201	isRecording { ^recorder.isRecording }
1202	pauseRecording { recorder.pauseRecording }
1203	stopRecording { recorder.stopRecording }
1204	prepareForRecord { |path, numChannels| recorder.prepareForRecord(path, numChannels) }
1205
1206	/* internal server commands */
1207
1208	bootInProcess {
1209		^options.bootInProcess;
1210	}
1211	quitInProcess {
1212		_QuitInProcessServer
1213		^this.primitiveFailed
1214	}
1215	allocSharedControls { |numControls=1024|
1216		_AllocSharedControls
1217		^this.primitiveFailed
1218	}
1219	setSharedControl { |num, value|
1220		_SetSharedControl
1221		^this.primitiveFailed
1222	}
1223	getSharedControl { |num|
1224		_GetSharedControl
1225		^this.primitiveFailed
1226	}
1227
1228	prPingApp { |func, onFailure, timeout = 3|
1229		var id = func.hash;
1230		var resp = OSCFunc({ |msg| if(msg[1] == id, { func.value; task.stop }) }, "/synced", addr);
1231		var task = timeout !? { fork { timeout.wait; resp.free;  onFailure.value } };
1232		addr.sendMsg("/sync", id);
1233	}
1234
1235	/* CmdPeriod support for Server-scope and Server-record and Server-volume */
1236
1237	cmdPeriod {
1238		addr = addr.recover;
1239		this.changed(\cmdPeriod)
1240	}
1241
1242	queryAllNodes { | queryControls = false |
1243		var resp, done = false;
1244		if(isLocal) {
1245			this.sendMsg("/g_dumpTree", 0, queryControls.binaryValue)
1246		} {
1247			resp = OSCFunc({ |msg|
1248				var i = 2, tabs = 0, printControls = false, dumpFunc;
1249				if(msg[1] != 0) { printControls = true };
1250				("NODE TREE Group" + msg[2]).postln;
1251				if(msg[3] > 0) {
1252					dumpFunc = {|numChildren|
1253						var j;
1254						tabs = tabs + 1;
1255						numChildren.do {
1256							if(msg[i + 1] >= 0) { i = i + 2 } {
1257								i = i + 3 + if(printControls) { msg[i + 3] * 2 + 1 } { 0 };
1258							};
1259							tabs.do { "   ".post };
1260							msg[i].post; // nodeID
1261							if(msg[i + 1] >= 0) {
1262								" group".postln;
1263								if(msg[i + 1] > 0) { dumpFunc.value(msg[i + 1]) };
1264							} {
1265								(" " ++ msg[i + 2]).postln; // defname
1266								if(printControls) {
1267									if(msg[i + 3] > 0) {
1268										" ".post;
1269										tabs.do { "   ".post };
1270									};
1271									j = 0;
1272									msg[i + 3].do {
1273										" ".post;
1274										if(msg[i + 4 + j].isMemberOf(Symbol)) {
1275											(msg[i + 4 + j] ++ ": ").post;
1276										};
1277										msg[i + 5 + j].post;
1278										j = j + 2;
1279									};
1280									"\n".post;
1281								}
1282							}
1283						};
1284						tabs = tabs - 1;
1285					};
1286					dumpFunc.value(msg[3]);
1287				};
1288				done = true;
1289			}, '/g_queryTree.reply', addr).oneShot;
1290
1291			this.sendMsg("/g_queryTree", 0, queryControls.binaryValue);
1292			SystemClock.sched(3, {
1293				if(done.not) {
1294					resp.free;
1295					"Remote server failed to respond to queryAllNodes!".warn;
1296				};
1297			})
1298		}
1299	}
1300
1301	printOn { |stream|
1302		stream << name;
1303	}
1304
1305	storeOn { |stream|
1306		var codeStr = switch(this,
1307			Server.default, { if(sync_s) { "s" } { "Server.default" } },
1308			Server.local,	{ "Server.local" },
1309			Server.internal, { "Server.internal" },
1310			{ "Server.fromName(" + name.asCompileString + ")" }
1311		);
1312		stream << codeStr;
1313	}
1314
1315	archiveAsCompileString { ^true }
1316	archiveAsObject { ^true }
1317
1318	getControlBusValue {|busIndex|
1319		if(serverInterface.isNil) {
1320			Error("Server-getControlBusValue only supports local servers").throw;
1321		} {
1322			^serverInterface.getControlBusValue(busIndex)
1323		}
1324	}
1325
1326	getControlBusValues {|busIndex, busChannels|
1327		if(serverInterface.isNil) {
1328			Error("Server-getControlBusValues only supports local servers").throw;
1329		} {
1330			^serverInterface.getControlBusValues(busIndex, busChannels)
1331		}
1332	}
1333
1334	setControlBusValue {|busIndex, value|
1335		if(serverInterface.isNil) {
1336			Error("Server-getControlBusValue only supports local servers").throw;
1337		} {
1338			^serverInterface.setControlBusValue(busIndex, value)
1339		}
1340	}
1341
1342	setControlBusValues {|busIndex, valueArray|
1343		if(serverInterface.isNil) {
1344			Error("Server-getControlBusValues only supports local servers").throw;
1345		} {
1346			^serverInterface.setControlBusValues(busIndex, valueArray)
1347		}
1348	}
1349
1350	*scsynth {
1351		this.program = this.program.replace("supernova", "scsynth")
1352	}
1353
1354	*supernova {
1355		this.program = this.program.replace("scsynth", "supernova")
1356	}
1357
1358}
1359