All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.sciss.synth.Server.scala Maven / Gradle / Ivy

/*
 *  Server.scala
 *  (ScalaCollider)
 *
 *  Copyright (c) 2008-2013 Hanns Holger Rutz. All rights reserved.
 *
 *  This software is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; either
 *  version 2, june 1991 of the License, or (at your option) any later version.
 *
 *  This software is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 *  General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public
 *  License (gpl.txt) along with this software; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *
 *  For further information, please contact Hanns Holger Rutz at
 *  [email protected]
 */

package de.sciss.synth

import io.{AudioFileType, SampleFormat}
import java.io.{File, IOException}
import de.sciss.osc.{Dump, Client => OSCClient, Packet, Transport, TCP, UDP}
import java.net.{DatagramSocket, InetAddress, InetSocketAddress, ServerSocket}
import collection.mutable

object Server {
   var default: Server  = null

   /**
    * The default file path to `scsynth`. If the runtime (system) property `"SC_HOME"` is provided,
    * this specifies the directory of `scsynth`. Otherwise, an environment (shell) variable named
    * `"SC_HOME"` is checked. If neither exists, this returns `scsynth` in the current working directory.
    */
   def defaultProgramPath = new File( sys.props.getOrElse( "SC_HOME", sys.env.getOrElse( "SC_HOME", "" )),
                                      "scsynth" ).getAbsolutePath

   /**
    * The base trait for `Config` and `ConfigBuilder` describes the settings used to boot scsynth in
    * realtime or non-realtime mode, as well as its server address and port.
    *
    * You obtain a `ConfigBuilder` by calling `Server.Config()`. This builder can then be mutated and
    * will be implicitly converted to an immutable `Config` when required.
    *
    * See `ConfigBuilder` for its default values.
    *
    * @see [[de.sciss.synth.Server.ConfigBuilder]]
    * @see [[de.sciss.synth.Server.Config]]
    */
   trait ConfigLike {
      /**
       * The path to `scsynth`, used when booting a server. This can be either a relative path
       * (relating to the JVM's working directory), or an absolute path.
       *
       * @see [[de.sciss.synth.Server#defaultProgramPath]]
       */
      def programPath:           String

      /**
       * The maximum number of control bus channels.
       */
      def controlBusChannels:    Int

      /**
       * The maximum number of audio bus channels. This includes the channels connected
       * to hardware (`outputBusChannels`) as well as all channels for internal routing.
       */
      def audioBusChannels:      Int

      /**
       * The number of connected audio hardware output channels. This does not need to
       * correspond to the actual number of channels your sound card provides, but can
       * be lower or higher, although a higher value doesn't have any effect as channel
       * indices above the number of channels of the sound card will be treated as
       * internal channels.
       */
      def outputBusChannels:     Int

      /**
       * The calculation block size. That is, the number of audio samples calculated en-bloc.
       * This corresponds with the control rate, such that
       * `controlRate := audioRate / blockSize`. It should be a power of two.
       */
      def blockSize:             Int

      /**
       * The audio hardware sampling rate to use. A value of `0` indicates that scsynth
       * should use the current sampling rate of the audio hardware. An explicit setting
       * will make scsynth try to switch the sound card's sample rate if necessary.
       */
      def sampleRate:            Int

      /**
       * The maximum number of audio buffers (for the `Buffer` class).
       */
      def audioBuffers:          Int

      /**
       * The maximum number of concurrent nodes (synths and groups).
       */
      def maxNodes:              Int

      /**
       * The maximum number of synth defs.
       */
      def maxSynthDefs:          Int

      /**
       * The maximum number of pre-allocated realtime memory in bytes. This memory
       * is used for many UGens such as `Limiter`, `DelayN` etc. It does not
       * affect dynamically allocated memory such as audio buffers.
       */
      def memorySize:            Int

      /**
       * The maximum number of concurrent connections between UGens in a single synth.
       * ScalaCollider performs a depth-first topological sorting of the synth defs,
       * so you should not worry too much about this value. It can become important
       * in very heavy channel expansions and mixdown.
       *
       * This value will be automatically increased if a more complex def is loaded
       * at startup, but it cannot be increased thereafter without rebooting.
       */
      def wireBuffers:           Int

      /**
       * The number of individual random number generators allocated.
       */
      def randomSeeds:           Int

      /**
       * Whether scsynth should load synthdefs stored on the harddisk when booted.
       */
      def loadSynthDefs:         Boolean

      /**
       * ?
       */
      def machPortName:          Option[ (String, String) ]

      /**
       * The verbosity level of scsynth. The standard value is `0`, while
       * `-1` suppresses informational messages, `-2` also suppresses many error messages.
       */
      def verbosity:             Int

      /**
       * An explicit list of paths where DSP plugins are found. Usually this is not
       * specified, and scsynth looks for plugins in their default location.
       */
      def plugInsPaths:          List[ String ]

      /**
       * An option to restrict access to files (e.g. for loading and saving buffers) to
       * a particular directory. This is a security measure, preventing malicious clients from
       * accessing parts of the harddisk which they shouldn't.
       */
      def restrictedPath:        Option[ String ]

      // ---- realtime only ----

      /**
       * (Realtime) Host address of scsynth, when trying to `connect` to an already running server on the net.
       */
      def host:                  String

      /**
       * (Realtime) UDP or TCP port used by scsynth.
       */
      def port:                  Int

      /**
       * (Realtime) Open Sound Control transport used by scsynth. (Either of `UDP` and `TCP`).
       */
      def transport:             Transport.Net

      /**
       * (Realtime) An option to enable particular input 'streams' or 'bundles' of a sound card.
       * This is a 'binary' String made of `'0'` and `'1'` characters.
       * If the string is `"01100"`, for example, then only the second and third input streams on
       * the device will be enabled.
       */
      def inputStreamsEnabled:   Option[ String ]

      /**
       * (Realtime) An option to enable particular output 'streams' or 'bundles' of a sound card.
       * This is a 'binary' String made of `'0'` and `'1'` characters.
       * If the string is `"01100"`, for example, then only the second and third output streams on
       * the device will be enabled.
       */
      def outputStreamsEnabled:  Option[ String ]

      /**
       * (Realtime) An option denoting the name of the sound card to use. On systems which distinguish
       * input and output devices (OS X), this implies that both are the same. Otherwise, you can
       * use the `deviceNames` method instead.
       *
       * @see deviceNames
       */
      def deviceName:            Option[ String ]

      /**
       * (Realtime) An option denoting the name of the input and output sound device to use. This is for
       * systems which distinguish input and output devices (OS X). If you use a single device both for
       * input and output (applies to most professional audio interfaces), you can simply use the
       * single string method `deviceName`.
       *
       * @see deviceName
       */
      def deviceNames:           Option[ (String, String) ]

      /**
       * (Realtime) The number of connected audio hardware input channels. This does not need to
       * correspond to the actual number of channels your sound card provides, but can
       * be lower or higher, although a higher value doesn't have any effect as channel
       * indices above the number of channels of the sound card will be treated as
       * internal channels.
       */
      def inputBusChannels:      Int

      /**
       * (Realtime) A value to adjust the sound card's hardware block size. Typically you will leave
       * this to `0` which means that the current block size is used. The block sizes supported depend
       * on the particular sound card. Lower values decrease latency but may increase CPU load.
       */
      def hardwareBlockSize:     Int

      /**
       * (Realtime) Whether to announce scsynth's OSC service via zero conf. See
       * [[http://en.wikipedia.org/wiki/Zero_configuration_networking Wikipedia]] for more details.
       */
      def zeroConf:              Boolean

      /**
       * (Realtime) The maximum number of client connections when using TCP transport.
       */
      def maxLogins:             Int

      /**
       * (Realtime) A requires session password when using TCP transport. When using TCP and the password option
       * is set, each client must send the correct password as the first command to the server, otherwise it is
       * rejected.
       */
      def sessionPassword:       Option[ String ]

      // ---- nonrealtime only ----

      /**
       * (Non-Realtime) Path to the binary OSC file.
       */
      def nrtCommandPath:        String
      /**
       * (Non-Realtime) Path to the audio input file used as audio input bus supplement.
       */
      def nrtInputPath:          Option[ String ]
      /**
       * (Non-Realtime) Path to the audio output file used as audio output bus supplement.
       */
      def nrtOutputPath:         String
      /**
       * (Non-Realtime) Audio file format for writing the output.
       */
      def nrtHeaderFormat:       AudioFileType
      /**
       * (Non-Realtime) Audio sample format for writing the output.
       */
      def nrtSampleFormat:       SampleFormat

      /**
       * Produces a command line for booting scsynth in realtime mode.
       */
      final def toRealtimeArgs    : List[ String ] = Config.toRealtimeArgs( this )

      /**
       * Produces a command line for booting scsynth in non-realtime mode.
       */
      final def toNonRealtimeArgs : List[ String ] = Config.toNonRealtimeArgs( this )

      /**
       * A utility method providing the audio bus offset for the start of
       * the internal channels. (simply the sum of `outputBusChannels` and `inputBusChannels`).
       */
      final def internalBusIndex: Int = outputBusChannels + inputBusChannels
   }

   object Config {
      /**
       * Creates a new configuration builder with default settings
       */
      def apply() : ConfigBuilder = new ConfigBuilder()

      /**
       * Implicit conversion which allows you to use a `ConfigBuilder`
       * wherever a `Config` is required.
       */
      implicit def build( cb: ConfigBuilder ) : Config = cb.build

      private[Server] def toNonRealtimeArgs( o: ConfigLike ): List[ String ] = {
         val b = List.newBuilder[ String ]

         // -N       <...other scsynth arguments>
         b += o.programPath
         b += "-N"
         b += o.nrtCommandPath
         b += o.nrtInputPath.getOrElse( "_" )
         b += o.nrtOutputPath
         b += o.sampleRate.toString
         b += o.nrtHeaderFormat.id
         b += o.nrtSampleFormat.id

         addCommonArgs( o, b )
         b.result()
      }

      private[Server] def toRealtimeArgs( o: ConfigLike ): List[ String ] = {
         val b = List.newBuilder[ String ]

         b += o.programPath
         o.transport match {
            case TCP =>
               b += "-t"
               b += o.port.toString
            case UDP =>
               b += "-u"
               b += o.port.toString
         }

         addCommonArgs( o, b )

         if( o.hardwareBlockSize != 0 ) {
            b += "-Z"
            b += o.hardwareBlockSize.toString
         }
         if( o.sampleRate != 0 ) {
            b += "-S"
            b += o.sampleRate.toString
         }
         if( o.maxLogins != 64 ) {
            b += "-l"
            b += o.maxLogins.toString
         }
         o.sessionPassword.foreach { pwd =>
            b += "-p"
            b += pwd
         }
         o.inputStreamsEnabled.foreach { stream =>
            b += "-I"
            b += stream
         }
         o.outputStreamsEnabled.foreach { stream =>
            b += "-O"
            b += stream
         }
         if( !o.zeroConf ) {
            b += "-R"
            b += "0"
         }
         o.deviceNames.foreach { case (inDev, outDev) =>
            b += "-H"
            b += inDev
            b += outDev
         }
         o.deviceName.foreach { n =>
            b += "-H"
            b += n
         }
         o.restrictedPath.foreach { path =>
            b += "-P"
            b += path
         }

         b.result()
      }

      private[Server] def addCommonArgs( o: ConfigLike, b: mutable.Builder[ String, _ ]) {
         if( o.controlBusChannels != 4096 ) {
            b += "-c"
            b += o.controlBusChannels.toString
         }
         if( o.audioBusChannels != 128 ) {
            b += "-a"
            b += o.audioBusChannels.toString
         }
         if( o.inputBusChannels != 8 ) {
            b += "-i"
            b += o.inputBusChannels.toString
         }
         if( o.outputBusChannels != 8 ) {
            b += "-o"
            b += o.outputBusChannels.toString
         }
         if( o.blockSize != 64 ) {
            b += "-z"
            b += o.blockSize.toString
         }
         if( o.audioBuffers != 1024 ) {
            b += "-b"
            b += o.audioBuffers.toString
         }
         if( o.maxNodes != 1024 ) {
            b += "-n"
            b += o.maxNodes.toString
         }
         if( o.maxSynthDefs != 1024 ) {
            b += "-d"
            b += o.maxSynthDefs.toString
         }
         if( o.memorySize != 8192 ) {
            b += "-m"
            b += o.memorySize.toString
         }
         if( o.wireBuffers != 64 ) {
            b += "-w"
            b += o.wireBuffers.toString
         }
         if( o.randomSeeds != 64 ) {
            b += "-r"
            b += o.randomSeeds.toString
         }
         if( !o.loadSynthDefs ) {
            b += "-D"
            b += "0"
         }
         o.machPortName.foreach { case (send, reply) =>
            b += "-M"
            b += send
            b += reply
         }
         if( o.verbosity != 0 ) {
            b += "-v"
            b += o.verbosity.toString
         }
         if( o.plugInsPaths.nonEmpty ) {
            b += "-U"
            b += o.plugInsPaths.mkString( ":" )
         }
      }
   }

   /**
    * @see [[de.sciss.synth.Server.ConfigBuilder]]
    * @see [[de.sciss.synth.Server.ConfigLike]]
    */
   final class Config private[Server]( val programPath: String, val controlBusChannels: Int, val audioBusChannels: Int,
                       val outputBusChannels: Int, val blockSize: Int, val sampleRate: Int, val audioBuffers: Int,
                       val maxNodes: Int, val maxSynthDefs: Int, val memorySize: Int, val wireBuffers: Int,
                       val randomSeeds: Int, val loadSynthDefs: Boolean, val machPortName: Option[ (String, String) ],
                       val verbosity: Int, val plugInsPaths: List[ String ], val restrictedPath: Option[ String ],
                       /* val memoryLocking: Boolean, */ val host: String, val port: Int, val transport: Transport.Net,
                       val inputStreamsEnabled: Option[ String ], val outputStreamsEnabled: Option[ String ],
                       val deviceNames: Option[ (String, String) ], val deviceName: Option[ String ],
                       val inputBusChannels: Int,
                       val hardwareBlockSize: Int, val zeroConf: Boolean, val maxLogins: Int,
                       val sessionPassword: Option[ String ], val nrtCommandPath: String,
                       val nrtInputPath: Option[ String ],
                       val nrtOutputPath: String, val nrtHeaderFormat: AudioFileType,
                       val nrtSampleFormat: SampleFormat )
   extends ConfigLike {
      override def toString = "ServerOptions"
   }

   /**
    * @see [[de.sciss.synth.Server.Config]]
    * @see [[de.sciss.synth.Server.ConfigLike]]
    */
  final class ConfigBuilder private[Server] () extends ConfigLike {
     /**
      * The default `programPath` is read from `defaultProgramPath`
      *
      * @see [[de.sciss.synth.Server#defaultProgramPath]]
      */
     var programPath:           String                     = defaultProgramPath
     /**
      * The default number of control bus channels is `4096` (scsynth default)
      */
     var controlBusChannels:    Int                        = 4096
     /**
      * The default number of audio bus channels is `128` (scsynth default)
      */
     var audioBusChannels:      Int                        = 128
     /**
      * The default number of output bus channels is `8` (scsynth default)
      */
     var outputBusChannels:     Int                        = 8
     /**
      * The default calculation block size is `64` (scsynth default)
      */
     var blockSize:             Int                        = 64
     /**
      * The default sample rate is `0` (meaning that it is adjusted to
      * the sound card's current rate; scsynth default)
      */
     var sampleRate:            Int                        = 0
     /**
      * The default number of audio buffers is `1024` (scsynth default)
      */
     var audioBuffers:          Int                        = 1024
     /**
      * The default maximum number of nodes is `1024` (scsynth default)
      */
     var maxNodes:              Int                        = 1024
     /**
      * The default maximum number of synth defs is `1024` (scsynth default)
      */
     var maxSynthDefs:          Int                        = 1024
     /**
      * The default memory size is `65536` (64 KB) (higher than scsynth's default of 8 KB)
      */
     var memorySize:            Int                        = 65536 // 8192
     /**
      * The default number of wire buffers is `256` (higher than scsynth's default of `64`).
      */
     var wireBuffers:           Int                        = 256 // 64
     /**
      * The default number of random number generators is `64` (scsynth default)
      */
     var randomSeeds:           Int                        = 64
     /**
      * The default setting for loading synth defs is `true` (scsynth default)
      */
     var loadSynthDefs:         Boolean                    = true
     /**
      * The default settings for mach port name is `None` (scsynth default)
      */
     var machPortName:          Option[ (String, String) ] = None
     /**
      * The default verbosity level is `0` (scsynth default)
      */
     var verbosity:             Int                        = 0
     /**
      * The default setting for plugin path redirection is `Nil`
      * (use standard paths; scsynth default)
      */
     var plugInsPaths:          List[ String ]             = Nil
     /**
      * The default setting for restricting file access is `None` (scsynth default)
      */
     var restrictedPath:        Option[ String ]           = None

//     var memoryLocking:         Boolean                    = false

//   // client only
//   var clientID:              Int                        = 0
//   var nodeIDOffset:          Int                        = 1000

     // ---- realtime only ----

     /**
      * (Realtime) The default host name is `127.0.0.1`
      */
     var host:                  String                     = "127.0.0.1"
     /**
      * (Realtime) The default port is `57110`.
      */
     var port:                  Int                        = 57110
     /**
      * (Realtime) The default transport is `UDP`.
      */
     var transport:             Transport.Net              = UDP
     /**
      * (Realtime) The default settings for enabled input streams is `None`
      */
     var inputStreamsEnabled:   Option[ String ]           = None
     /**
      * (Realtime) The default settings for enabled output streams is `None`
      */
     var outputStreamsEnabled:  Option[ String ]           = None

     /**
      * (Realtime) The default device name is `None` (scsynth default; it will
      * use the system default sound card)
      */
     private var deviceNameVar:  Option[ String ] = None
     /**
      * (Realtime) The default input/output device names is `None` (scsynth default; it will
      * use the system default sound card)
      */
     private var deviceNamesVar: Option[ (String, String) ] = None

     def deviceName:            Option[ String ]           = deviceNameVar
     def deviceNames:           Option[ (String, String) ] = deviceNamesVar
     def deviceName_=( value: Option[ String ]) {
        deviceNameVar = value
        if( value.isDefined ) deviceNamesVar = None
     }
     def deviceNames_=( value: Option[ (String, String) ]) {
        deviceNamesVar = value
        if( value.isDefined ) deviceNameVar = None
     }

     /**
      * (Realtime) The default number of input bus channels is `8` (scsynth default)
      */
     var inputBusChannels:      Int                        = 8
     /**
      * (Realtime) The default setting for hardware block size is `0` (meaning that
      * scsynth uses the hardware's current block size; scsynth default)
      */
     var hardwareBlockSize:     Int                        = 0
     /**
      * (Realtime) The default setting for zero-conf is `false` (other than
      * scsynth's default which is `true`)
      */
     var zeroConf:              Boolean                    = false // true
     /**
      * (Realtime) The maximum number of TCP clients is `64` (scsynth default)
      */
     var maxLogins:             Int                        = 64
     /**
      * (Realtime) The default TCP session password is `None`
      */
     var sessionPassword:       Option[ String ]           = None

     // ---- nonrealtime only ----


     var nrtCommandPath:        String                     = ""
     var nrtInputPath:          Option[ String ]           = None
     var nrtOutputPath:         String                     = ""
     var nrtHeaderFormat:       AudioFileType              = AudioFileType.AIFF
     var nrtSampleFormat:       SampleFormat               = SampleFormat.Float

     /**
      * Picks and assigns a random free port for the server. This implies that
      * the server will be running on the local machine.
      *
      * As a result, this method will change this config builder's `port` value.
      * The caller must ensure that the `host` and `transport` fields have been
      * decided on before calling this method. Later changes of either of these
      * will render the result invalid.
      *
      * This method will fail with runtime exception if the host is not local.
      */
     def pickPort() {
        require( isLocal )
        transport match {
           case UDP =>
              val tmp = new DatagramSocket()
              port = tmp.getLocalPort
              tmp.close()
           case TCP =>
              val tmp = new ServerSocket( 0 )
              port = tmp.getLocalPort
              tmp.close()
        }
     }

     /**
      * Checks if the currently set `host` is located on the local machine.
      */
     def isLocal : Boolean = {
        val hostAddr = InetAddress.getByName( host )
        hostAddr.isLoopbackAddress || hostAddr.isSiteLocalAddress
     }

     def build : Config = new Config(
        programPath, controlBusChannels, audioBusChannels, outputBusChannels, blockSize, sampleRate, audioBuffers,
        maxNodes, maxSynthDefs, memorySize, wireBuffers, randomSeeds, loadSynthDefs, machPortName, verbosity,
        plugInsPaths, restrictedPath, /* memoryLocking, */ host, port, transport, inputStreamsEnabled, outputStreamsEnabled,
        deviceNames, deviceName, inputBusChannels, hardwareBlockSize, zeroConf, maxLogins, sessionPassword,
        nrtCommandPath,
        nrtInputPath, nrtOutputPath, nrtHeaderFormat, nrtSampleFormat )
  }

   @throws( classOf[ IOException ])
   def boot: ServerConnection = boot()()

   @throws( classOf[ IOException ])
   def boot( name: String = "localhost", config: Config = Config().build,
             clientConfig: Client.Config = Client.Config().build )
           ( listener: Model.Listener = Model.EmptyListener ) : ServerConnection = {
      val sc = initBoot( name, config, clientConfig )
      if( !(listener eq Model.EmptyListener) ) sc.addListener( listener )
      sc.start()
      sc
   }

   private def initBoot( name: String = "localhost", config: Config = Config().build,
             clientConfig: Client.Config = Client.Config().build ) = {
      val (addr, c) = prepareConnection( config, clientConfig )
//c.dump()
      new impl.Booting( name, c, addr, config, clientConfig, true )
   }

   @throws( classOf[ IOException ])
   def connect: ServerConnection = connect()()

   @throws( classOf[ IOException ])
   def connect( name: String = "localhost", config: Config = Config().build,
                clientConfig: Client.Config = Client.Config().build )
              ( listener: Model.Listener = Model.EmptyListener ) : ServerConnection = {
      val (addr, c) = prepareConnection( config, clientConfig )
      val sc = new impl.Connection( name, c, addr, config, clientConfig, true )
      if( !(listener eq Model.EmptyListener) ) sc.addListener( listener )
      sc.start()
      sc
   }

   def run( code: Server => Unit ) { run()( code )}
   
   /**
    *    Utility method to test code quickly with a running server. This boots a
    *    server and executes the passed in code when the server is up. A shutdown
    *    hook is registered to make sure the server is destroyed when the VM exits.
    */
   def run( config: Config = Config().build )( code: Server => Unit ) {
//      val b = boot( config = config )
      val sync = new AnyRef
      var s : Server = null
      val sc = initBoot( config = config )
      sc.addListener {
         case ServerConnection.Running( srv ) => sync.synchronized { s = srv }; code( srv )
      }
      Runtime.getRuntime.addShutdownHook( new Thread { override def run() { sync.synchronized {
         if( s != null ) {
            if( s.condition != Server.Offline ) s.quit()
         } else sc.abort()
      }}})
      sc.start()
   }

//   @throws( classOf[ IOException ])
//   def dummy: Server = dummy()

   /**
    * Creates an unconnected server proxy. This may be useful for creating NRT command files.
    * Any attempt to try to send messages to the server will fail.
    */
   @throws( classOf[ IOException ])
   def dummy( name: String = "dummy", config: Config = Config().build,
                clientConfig: Client.Config = Client.Config().build ) : Server = {
      val (addr, c) = prepareConnection( config, clientConfig )
      new impl.ServerImpl( name, c, addr, config, clientConfig )
   }

   @throws( classOf[ IOException ])
   private def prepareConnection( config: Config, clientConfig: Client.Config ) : (InetSocketAddress, OSCClient) = {
      val addr = new InetSocketAddress( config.host, config.port )
      val clientAddr = clientConfig.addr getOrElse {
          if( addr.getAddress.isLoopbackAddress )
             new InetSocketAddress( "127.0.0.1", 0 ) else
             new InetSocketAddress( InetAddress.getLocalHost, 0 )
      }
      val c    = createClient( config.transport, addr, clientAddr )
      (addr, c)
   }
   
   def allocPort( transport: Transport ) : Int = {
      transport match {
         case TCP =>
            val ss = new ServerSocket( 0 )
            try {
               ss.getLocalPort
            } finally {
               ss.close()
            }

         case UDP =>
            val ds = new DatagramSocket()
            try {
               ds.getLocalPort
            } finally {
               ds.close()
            }

         case other => sys.error( "Unsupported transport : " + other.name )
      }
   }

   def printError( name: String, t: Throwable ) {
      println( name + " : " )
      t.printStackTrace()
   }

   implicit def defaultGroup( s: Server ) = s.defaultGroup

   abstract sealed class Condition
   case object Running extends Condition
   case object Offline extends Condition
   private[synth] case object NoPending extends Condition

   case class Counts( c: osc.StatusReplyMessage )

   private def createClient( transport: Transport.Net, serverAddr: InetSocketAddress,
                             clientAddr: InetSocketAddress ) : OSCClient = {
//      val client        = OSCClient( transport, 0, addr.getAddress.isLoopbackAddress, osc.ServerCodec )
//println( "transport = " + transport + " ; server = " + serverAddr + " ; client = " + clientAddr )
      val client        = transport match {
         case UDP =>
            val cfg                 = UDP.Config()
            cfg.localSocketAddress  = clientAddr
            cfg.codec               = osc.ServerCodec
            cfg.bufferSize          = 0x10000
            UDP.Client( serverAddr, cfg )
         case TCP =>
            val cfg                 = TCP.Config()
            cfg.codec               = osc.ServerCodec
            cfg.localSocketAddress  = clientAddr
            cfg.bufferSize          = 0x10000
            TCP.Client( serverAddr, cfg )
      }
//      client.connect()
      client
   }
}

sealed trait ServerLike extends Model {
   def name: String
   def config: Server.Config
   def addr: InetSocketAddress
}

object ServerConnection {
   sealed abstract class Condition
   case class Preparing( server: Server ) extends Condition
   case class Running( server: Server ) extends Condition
   case object Aborted extends Condition
}
trait ServerConnection extends ServerLike {
//   def server : Future[ Server ]
   def abort() : Unit // : Future[ Unit ]
}

trait Server extends ServerLike {
   server =>

   import Server._

   def clientConfig: Client.Config

   def rootNode : Group
   def defaultGroup : Group
   def nodeManager : NodeManager
   def bufManager : BufferManager

   def isLocal : Boolean
   def isConnected : Boolean
   def isRunning : Boolean
   def isOffline : Boolean

   def nextNodeID(): Int
   def nextSyncID() : Int

   def allocControlBus( numChannels: Int ) : Int
   def allocAudioBus( numChannels: Int ) : Int
   def freeControlBus( index: Int ) : Unit
   def freeAudioBus( index: Int ) : Unit

   def allocBuffer( numChannels: Int ) : Int
   def freeBuffer( index: Int ) : Unit

   def !( p: Packet ) : Unit

//   /**
//    * Sends out an OSC packet that generates some kind of reply, and
//    * returns immediately a `RevocableFuture` representing the parsed reply.
//    * This parsing is done by a handler which is registered.
//    * The handler is tested for each incoming OSC message (using its
//    * `isDefinedAt` method) and invoked and removed in case of a
//    * match. Note that the caller is responsible for timing out
//    * the handler after a reasonable time. To do this, the
//    * method `revoke` on the returned future must be called, which
//    * will silently unregister the handler.
//    *
//    * '''Warning''': It is crucial that the Future is awaited
//    * only within a dedicated actor thread. In particular you must
//    * be careful and aware of the fact that the handler is executed
//    * on the OSC receiver actor's body, and that you must not
//    * try to await the future from ''any'' handler function
//    * registered with OSC reception, because it would not be
//    * possible to pull the reply message of the OSC receiver's
//    * mailbox while the actor body blocks.
//    *
//    * @param   p        the packet to send out
//    * @param   handler  the handler to match against incoming messages
//    *    or timeout
//    * @return  the future representing the parsed reply, and providing
//    *    a `revoke` method to issue a timeout.
//    *
//    * @see  [[scala.actors.Futures]]
//    */
//   def !![ A ]( p: Packet, handler: PartialFunction[ Message, A ]) : RevocableFuture[ A ]

   /**
    * Sends out an OSC packet that generates some kind of reply, and
    * returns immediately. It registers a handler to parse that reply.
    * The handler is tested for each incoming OSC message (using its
    * `isDefinedAt` method) and invoked and removed in case of a
    * match. If the handler doesn't match in the given timeout period,
    * it is invoked with message `TIMEOUT` and removed. If the handler
    * wishes not to do anything particular in the case of a timeout,
    * it simply should not add a case for `TIMEOUT`.
    *
    * @param   timeOut  the timeout in milliseconds
    * @param   p        the packet to send out
    * @param   handler  the handler to match against incoming messages
    *    or timeout 
    *
    * @see  [[de.sciss.synth.osc.TIMEOUT]]
    */
   def !?( timeOut: Long, p: Packet, handler: PartialFunction[ Any, Unit ]) : Unit

   def counts : osc.StatusReplyMessage

   def sampleRate : Double
  
   def dumpTree( controls: Boolean = false ) : Unit
  
   def condition : Condition

   def startAliveThread( delay: Float = 0.25f, period: Float = 0.25f, deathBounces: Int = 25 ) : Unit

   def stopAliveThread() : Unit

   def queryCounts() : Unit

   final def syncMsg : osc.SyncMessage = syncMsg() // XXX TODO : should be removed
   final def syncMsg( id: Int = nextSyncID() ) = osc.SyncMessage( id )

   def dumpOSC( mode: Dump = Dump.Text ) : Unit

   def quit() : Unit

   final def quitMsg = osc.ServerQuitMessage

   def dispose() : Unit

   private[synth] def addResponder( resp: osc.Responder ) : Unit
   private[synth] def removeResponder( resp: osc.Responder ) : Unit

   override def toString = "<" + name + ">"
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy