OSC Messages

Many people have been using message passing not only to control Max/PD objects, but also to interact with processes living outside MAX/PD such as CSound, SuperCollider, etc.

To make their life easier, Antescofo comes with a set of builtin OSC services. The OSC protocol can be used to interact with external processes using UDP messages. It can also be used to make two Antescofo objects interact within the same patch or even used internally within a single Antescofo object (the program emitting messages to itself).

The management of OSC messages is achieved in Antescofo through 3 primitives oscsend, oscreceive and osc_client:


OSCSEND

This keyword introduces the declaration of a named OSC output channel of communication. The declaration takes the following forms:

          oscsend name host : port msg_prefix
          oscsend name @global host : port msg_prefix

          oscsend name host : port
          oscsend name @global host : port

The oscsend command defines a new OSC channel with name name. As soon as the OSC channel is declared, it can be used to send messages using name as the message receiver, using exactly the same syntax as MAX or PD messages.

Note that sending a message to the receiver name before the definition of the corresponding output channel is interpreted as sending a message to MAX or PD receiver.

The sequence host : port msg_prefix defines the target of the osc send command. Several targets can be specified for the same oscsend name, see below.

The parameters of an oscsend command are as follows:

All these parameters can be expressions that are evaluated dynamically when the oscscsend action is performed.

Sending OSC messages

Sending an OSC message takes a form similar to sending a message to MAX or PD:

          name arg₁ arg₂ ...

where name is the name specified in an oscsend command. This action construct and send the osc message

          msg_prefix arg₁ arg₂ ...

where msg_prefix is the OSC address declared for name. If the message prefix is not specified for name, the first argument of the message is used as the prefix and the action constructs and send the OSC message

          arg₁  arg₂  ...

In this case, arg₁ must evaluate into a string. N.B.: the OSC protocol explicitly requires that the prefix starts with the character \.

Sending several messages with one receiver

Sending an osc message accept the comma syntax used for sending simultaenously several messages to the same Max/PD receiver:

      oscsend send : 345678 "/header"
      ; ...
      send  1 2 3,  4 5 6,  7 8 9

will send three messages to the osc receiver send.

More generally the constraints of Max/PD messages apply on OSC messages (a message must fit on one line or backslash are explicitly used to break the message on multiple lines; osc message accept the same attributes as Max/PD message, etc.).

Multiple OscSend construct referring to the same host and port

It is perfectly legal to have several oscsend declaration refering to the same host and port. This can be used to handle different message's prefixes directed to the same host+port: define an oscsend channel for each prefixes (an alternative is to define a channel without prefix and give the prefix when sending the message).

Refering to multiple targets

The oscsend action accepts the specification of a list of destinations. Each message sent with the oscsend name is sent to all the addresses, achieving a (poor) kind of multicast:

       oscsend send2m "m1.ircam.fr" : 34567 "/antescofo/cmd", \
                      "m2.ircam.fr" : 34567 "/antescofo/cmd", \
                      "m3.ircam.fr" : 34567 "/antescofo/cmd", \
                      "m4.ircam.fr" : 34567 "/antescofo/cmd"

with this declaration, a message send2m arg₁ arg₂ ... will send the data arg₁ arg₂ ... to four hosts simultaneously (the backslash at the end of the line is simply used to break the long definition on multiple lines).

In addition, further oscsend commands with the same name, simply add the specified addresses to the targets of the message. So

     oscsend send2m "m1.ircam.fr" : 34567 "/antescofo/cmd"
     send2m 1
     ; ...
     oscsend send2m "m2.ircam.fr" : 34567 "/antescofo/cmd"
     send2m 2

will send 1 to host m1.ircam.fr and later will send the value 2 to hosts m1.ircam.fr and m2.ircam.fr.

The lifetime of the target addresses depends of the scope of the oscsend command used to add the address, see below.

Hirerachical message prefix

The character / is accepted in an identifier, so the usual hierarchical name used in OSC message prefixes can be used to identify the output channels. For instance, the declarations:

          oscsend extprocess/start test.ircam.fr : 3245 "/start"
          oscsend extprocess/stop  test.ircam.fr : 3245 "/stop"

can be used to later invoke

          0.0 extprocess/start "filter1"
          1.5 extprocess/stop "filter1"

Computing the name of an OSCSend channel

If the oscsend name is the result of a complex computation, then the @command construction must be used to refer to the command name. For example :

      $host := @compute_some_machine_name(...)
      $name := "reverb_" + $host + "--" + $port
      oscsend $name $host : $port "/reverb"
      ; ...
      @command($name) 10 
      reverb_mac1--34567 14
      reverb_mac2--34567 18

Notice the oscsend name is not part of an osc message, it is simply the identifier used internally to the Antescofo programm to refer to an external address and an optional prefixe. In the previous example, a name is forged because it is explicitly used through known identifier reverb_mac1--34567and reverb_mac2--34567 in the program. It can also be refered trough a computation as in @command($name).

Scope of OSC sending command

OscSend action creates a new name to send osc messages. This name follows the scoping rules used for variables and the hierarchy of execs: it can be used in the current exec and its children, unless hidden by a new oscsend definition.

This means that the same identifier can refer to different delivery addresses. This is especially usefull for object and processes : the name given to an oscsend command in a process instance always refers to the delivery addresse computed in the process instance, even if the same name is used for all instances.

At the termination (including all its children) of the exec of definition of the OSC command, the corresponding network resources are released.

Here is an example :

        forall $cpt in (10)
        {
            oscsend ping "webservice.ircam.fr" : (34560+$cpt) "/ping"
            ::P()
        }

Withing ::P the command ping can be used to send an osc message (even if the corresponding oscsend is not in scope of ::P but in an enclosing scope).

Notice the destination of the message depends on the process instance: the ith instance refers to the port 34560+i on the host "webservice.ircam.fr" because of the oscsend command in the forall body. The network resources allocated to reach this host are released when the corresponding instance of ::P die.

The @global optionnal attribute is used in an oscsend command to escape the current scope: with this attribute, the receiver specified by the osc send command has a global scope and can be accessed from anywhere after its definition.

Interpretation of Antescofo message receivers as OSC or Max/PD or file receivers

The definition of an oscsend name id prohibits sending Max/PD message to receivers with the same identifier id in the scope of the oscsend action. As a matter of fact, the three constructions:

share the same syntax. Antescofo uses the message identifier to known which construction is used (relatively to the current scope).

Max/PD receivers are not predefined, contrary to the other kinds of messages: receivers are declared with the oscsend command and output files are declared using the [openout] construction. The handling of messages first looks for an osc receivers taking the scope into account. If there is no such receivers, a receiver for writing in a file is searched. Finally, if there is no such receivers, the message is handled as a message for Max or PD.

The previous example, involving a process, outlines that looking for OSC name is a dynamic process not a syntactic one. this makes possible to write generic procedure whose messages get their actual interpretation following the context of execution.

Full syntax of oscsend

So, the full syntax of an oscsend declaration is defined by the following diagram (the port expression must evaluate into an integer, and the prefix expression into a string):


OSCRECV

This keyword introduces the declaration of an input channel of communication. The declaration takes the general form

oscrecv name target prefix callback

where:

An incomming OSC message that match the target specification and the prefix specification of an OSC message, triggers the associated callback. If there is no matching osc receivers, the message is simply ignored by Antescofo.

The parameter of an OSC receiver specification can be expression.

Target specification of an OSC receiver

The target of an OSC receiver defines an OSC input channel and is specified by one of the three following forms:

          host : port 
          : port 
          port

where port is the mandatory port number of the OSC messages and the optional host is the IP address of the sender. This address can take three forms:

If this argument is not provided but a colon : is present, then localhost is assumed. Notice that localhost is also equivalent to IP 127.0.0.1.

Nota Bene: if there is no host specification nor a colon, then the osc receiver accepts all messages incomming through the specified port, irrespectively of the host of the senders. This is useful and makes possible to feed an osc input channel with messages coming from several sources (e.g., merging the data stream comming from several sensors).

Message prefix of an OSC receiver

The prefix of an OSC receiver specifies the header of the matching messages. The prefix can take two forms: either a * or specific prefix given by an identifier (more generally, an expression evaluating into a string).

If a specific prefix is specified, the receiver handles only messages with that OSC address. Note that for a given OSC input channel, the message prefixes have to be all different.

If * is specified, then the receiver handles all messages irrespectively of their prefixes. Such receiver is called the catch-all receiver of the OSC input channel.

It is perfectly legal to have multiple oscrecv definition refering to the same port but only one catch-all receiver is allowed per port.

Callback

When an OSC message is received, its sender, the reception port and the message prefix are used to find the relevant input channel and the associated callback is activated. If there is no OSC receivers with the same prefix, the catch-all receiver, is used if it exists. If for a given input channel there is no adequate receivers nor a catch-all rule, a warning is signaled2.

The callback can be specified by a process (or a function) or by a sequence of variables $v, $v... :

If a matched message has more arguments than the variables specified in the receivers, the remaining message's arguments are gathered into a tab assigned to the last variable. Otherwise, if there is less arguments than variables, the remaining variables are set to undef (and a warning is emitted). The same rule applies mutatis mutandis for callback specified with a a process (process parameters are handled like the receiver's variables).

Nota Bene: for a catch-all receiver, there is only one assigned variable and this variable refers to a tab which gathers the message prefix (as the first tab element) and the message's arguments.

If the callback of a receiver is a process/function, the call of the callback is always done with the right number of parameters. So there is no partial application.

Scope of an OscRecv

The reception and the processing of incoming OSC message is active as soon as the input channel is declared.

If the callback is specified using a process or a function, the listening process remains open until the next antescofo::stop command. It can be stopped explicitly using the OSCOFF command.

If the callback relies on variables, the listening process is terminated as soon as one of the variable declared in the oscrecv command becomes inaccessible. Local variables can be used in an oscrecv declaration, e.g. in a proc or in an obj. Together with the possibility to compute the parameters of the reception, it makes possible to write generic obj to handle incomming osc message with listening process tied to the lifetime of the obj.

Dispatching incomming messages

OSC receivers provides a basic way to dispatch and process messages following their sender and their header: a process ::P can be associated to the handling of the messages with a specific header and ::P is called each time such a message is received on the input channel.

More complex dispatch and message handling can be achieved using callback specified through a set of variable, relying on a whenever construction to trigger a specific activity.

The typical handling of OSC incomming messages through variables takes the following form:

     oscrecv receive_command 34567 "/cmd" $arg $arg $arg ...
     whenever ( $arg )
     {
         ; process $arg₁ $arg₂ $arg₃ ... following some conditions
     }

refers to the whenever compound action to have a complete description of this mechanism. For example, suppose that they are distinct corrective actions to apply when an argument is above some level. This can be implemented as:

     oscrecv receive_command 34567 "/cmd" $paramX $paramY
     whenever ( $paramX )
     {
         if ($levelX < $paramX) { ::correctX($paramX) }
         if ($levelY < $paramY) { ::correctY($paramY) }
     }

It is enough to watch one of the variables of the OSC receiver callback as the condition of the whenever because all variables in the callback of an OSC receiver are assigned simultaneously.

Notice that message header is part of an oscrecv declaration. It means that OSC messages with different prefixes can be sent to the same port andd are handled in the antescofo program with different oscrecv actions:

     oscrecv receive_command 34567 "/start" $arg
     whenever StartActivity ($cmd)
     {
         ; starts some activity using $arg information
     }

     oscrecv abort_command 34567 "/abort" $abort
     whenever ($abort)
     {
         abort StartActivity
     }

     oscrecv ignored_command 34567 * $dummy_cmd
     whenever ($dummy_cmd)
     {
         print message ($dummy_cmd[0]) not handled
     }

Here, messages corresponding to start and abort commands are sent to the port 34567. OSC messages with another prefix are handled by the default receiver of the port.

OSC messages have the same constraint as Max/PD messages : they cannot be empty. So the variable $abort is a dummy variable used solely for triggering the whenever which aborts the other whenever used to implement the start.

If an OSC input channel is specified without prefix, the dispatch can be done by inspecting the first tab element:

     oscrecv receive_command 34567 * $message
     whenever EvalCommand ($message)
     {
         switch ($message[0])
         {
              case "/start":
              ; process $message[1] ... $message[N] to implement /cmd

              case "/abort":
              ; etc.
         }
     }

Callback using process or function can be implemented using the callback with variables. As a matter of fact,

     oscrecv in : 54321 "/header" ::P

is really a short-cut for

     oscrecv in : 54321 "/header"  $v, $v ...
     whenever ($v == $v)
     {
                ::P($v, $v ...)
     }

where the variable $v, $v... are fresh (i.e. does not appear elsewhere in the program). The whenever is triggered for the handling of the matched messages.

Full syntax of oscrecv

So, the full syntax of an oscsrecv declaration is defined by the following diagram:


OSC_CLIENT

The osc_client declaration combines the oscsend and oscrecv declarations to specify an OSC i/o channel. The name of the command can be used to send OSC messages to a receiver; called the server, while the callback is activated to handle incomming messages from the srever. The Antescofo program act as a client in a server-client relationships: the server answer to the requests of the client but do not engage in an interaction which is not initiated by the client.

The syntax of the command is the following:

An osc_client does not specify a message prefix. So when a message is sent, the first argument of the message is used as the header, and the rest of the parameters are the arguments of the OSC message.

Communicating with the SuperCollider typically uses an asco_client:

        // launching the SuperCollider server and
        // tell him to listen the requests on port 57110
        _ := @system("scsynth -u 57110 &")

        // open an osc communication channel in client mode to SuperColider
        // $server receives the answer comming from the SuperCollider server
        osc_client scServer localhost:57110 * $server

        // watch the variable $server to react to the server's communication
        whenever ( $server )
        {
            print "ANSWER: " $server
        }

        // initiate the connection with SuperCollider
        // by sendind the "/notify" command
        scServer "/notify" 1  ; the server will answer /done /notify"


OSCOFF

This commands take the name of an input channel or an output channel. This command is used to stop earlier the osc facilities bound to an osc name and to release earlier the associated network resources.

Such actions are sometimes needed,e.g. when it is convenient to associate the reception of osc messages to global variables but where the period of receptions is smaller than the entire piece.


Conversion between OSC types and Antescofo types

Sending (or receiving) an OSC message implies the conversion of an Antescofo value into an OSC value (or the reverse). The conversion is applied following the following mapping

Antescofo value OSC value
bool bool
int int32 see function @set_osc_handling_int64
int int64 see function @set_osc_handling_int64
Float float see function @set_osc_handling_double
Float double see function @set_osc_handling_double
string string
string symbol
tab see function @set_osc_handling_tab

Remarks




  1. Cf. IP addressing 

  2. If there is no OSC receiver defining the input channel on which the message i received, no warning is signaled and the message is ignored by Antescofo