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