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
, oscrecv
and osc_client
:
-
oscsend defines an OSC output channel used to send messages to a set of OSC receivers specified by an IP address1, a port number and, optionally, a predefined message header.
-
oscrecv defines an OSC input channel used to handle the OSC messages incomming to a specified port. Optionnaly a message header can be specified to restrict the message handled to those with this header.
-
osc_client defines an OSC channel for duplex communication with a server (typically, a SuperCollider server).
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:
-
name
is a simple identifier or an expression (which evaluates to a string) and refers to the output channel (used later to send messages). -
@global
is an optional qualifier used to force the scope of the command. See paragraph below. -
host
is the optional IP address (in the formnn.nn.nn.nn
wherenn
is an integer) or the symbolic name of the host (in the form of a simple identifierlocalhost
or an expression returning a string (like "test.ircam.fr"). If this argument is not provided, the localhost (that is, IP 127.0.0.1) is assumed1: this address represents the computer on which Antescofo is running. -
port
is an expression defining the mandatory number of the port where the message is routed (e.g. between 49152 and 65535, see List of TCP and UDP port numbers). -
msg_prefix
is the OSC address in the form of a string that will prefix the OSC message send to this channel. We use indifferently the terms message prefix, message address or message header to refer to this OSC address.
This prefix is optionnal: when sending an OSC message, if the prefix is not specified, the first argument of the message is used to define the prefix (cf. paragraph below).
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--34567
and 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:
-
sending an osc message,
-
sending a Max/PD message,
-
and writing in a file
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:
-
name is the identifier of the input channel and can used later to stop explcitly the channel
-
target is the specification of the senders of the handled messages
-
prefix is the specification of the headers of the handled messages
-
and callback defines the reaction to an incomming message.
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:
-
nn.nn.nn.nn
wherenn
is an integer -
the identifier
localhost
which refers to the local host (the computer on which antescofo is running) -
an expression returning a string (like "test.ircam.fr").
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₂
... :
-
for the former case, the process (resp. function) is called to handle the incomming message. The arguments of the process call (resp. function call) are the arguments of the OSC message.
-
for the latter case, the arguments of the message are automatically assigned to the 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
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
-
Antescofo value types that are not present in the table are not handled (e.g. proc or nim).
-
The default handling of Antescofo integers is to send them as 32 bits integers, the only precision required by the OSC protocol. A call to
@set_osc_handling_int64(true)
switches this default behavior to send 64 bits integers instead. Notice that this feature is not implemented in all OSC packages. See function @set_osc_handling_int64. -
The default handling of Antescofo float is to sending them as floats (32 bits representation), the precision required by the OSC protocol. A call to
@set_osc_handling_double(true)
switches this default behavior to send double (64 bits representation) instead. Notice that this feature is not implemented in all OSC packages. See function @set_osc_handling_double. -
The handling of tab can be changed using the @set_osc_handling_tab functions. By default, Antescofo sends the elements of a tab as the successive arguments of a message, without using the OSC array facilities. A call to
@set_osc_handling_tab(true)
switches the behavior to rely on the array feature present in OSC v1.1. A call to@set_osc_handling_tab(false)
switches to the default behavior (tab flattening). -
the system functions @set_osc_handling_int64, @set_osc_handling_double and @set_osc_handling_tab change the translation of Antescofo type into osc types globally, i.e. for all osc send commands, and dynamically. The behavior can be specified in a finer way, receivers by receivers, but statically at the
oscsend
declaration, using the attributes (no argument):@handling_int64
@handling_double
@handling_tab
-
Cf. IP addressing ↩↩
-
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. ↩