Interfacing with Open Stage Control


This how-to details a possible coupling between Open Stage Control and Antescofo through a simple example available here.

Open Stage Control is a libre and modular OSC / MIDI controller. The functionnalities are similar to touchOSC, Lemur or Mira:

  • Open Stage Control allows the building (by drawing) of user interfaces made of various widgets (button, switch, plot area, xy pads, sliders of various kinds).

  • A user interface can be accessed as a web page or as a standalone application on linux, Max or Windows plateforms. Because it can be embedded in a web page, the user interface can be run on a variety of device (like smart phones or tablets).

  • When actionned, the widgets send OSC messages to a final application. Here, the application will be Antescofo. The information received can be used to alter the computation performed by Antescofo. Antescofo can also send OSC messages to the widget to change their appearance.

  • The interface and the final application can reside on different machines, allowing remote control of an Antescofo program.

The architecture and the modus operandi is sketched in the following schema:

Open Sound Control interfaced with Antescofo

  1. The Open Stage Control server is launched as an app or as a command line. The server is responsible for sending and receiving all osc/midi messages, and act as a web server that serves the clients web application.

  2. The launcher provides a simple way to configure and start the server. It appears whenever the server is not launched from a terminal or without being configured.

  3. Once launched, a new client can be started or the configuration of an old one can be loaded. A client is a web application that display controls called widgets. The client can be launched by the server or can be a web page in a browser (the browser contact the server through a dedicated url).
    The client has two modes: the edition mode and the normal functionning mode. Cmde-E can be used to switch between the two modes. There are also a menu to operate on a client that is accessible by clicking on the three vertical dot at the left of the top banner of the client window.
    A client is edited to add new widgets, to change their geometry, etc. The client configuration, called a sessiion can be saved as a Json file that can be used latter to configure a client.

  4. Latter, the server can be launched again, by hand (as an app or as a terminal command) or directly from antescofo (which uses the terminal command). A new client can be launched and configured using a previous session.

  5. A final user can interact with the clients by manipulating knobs and faders, by touching the pads area. The various widgets offer a rich familly of interactions.

  6. Antescofo receives the notification of these interaction through OSC messages and take the corresponding action. It can also alter rthe state of the interface by sending messages to the widgets.

The distinction between steps (1-3) and steps (4-6) is artificial. Once a session is launched the client and Antescofo can interact and the client ca be switched in edition mode and back, anytime, relying on the same server.

The programmation of the coupling between Open Stage Control and Antescofo is illustrated through a simple example available here. The example assumes that the demo is running on a Mac and that Open Stage Control has been downloaded1 and is intalled in /Applications.

  • In your usual patch, load the score example1.asco.txt in the downloaded directory.

  • The example includes a small library libOpenStageControl.asc.txt located in the smae directory. This library defines an object obj::OpenStageControl used to communicate with a session.

  • Start or play the score: the example launches automatically a server qnd open a predefined session (the session is stored in the same diretory as the example.

  • During the functionning, you may observe Antescofo sending values to the client and aslo Antescofo reacting to the message received when you manipulate the interface. For instance, when you update the daer, Antescofo react to the message received by changing the text displeyd in the text area nearby.

  • You can also visit the url http://127.0.0.1:8080 using a web browser. The web page that opens is similar to the interface displayed by the client and the changes are synchronized between the two. The messages triggered by the manipulation of the widgets in the web page are also sent to Antescofo and the Antescofo messages are forwarded to all clients, including teh browser.

In the rest of this section, we will detail the coding of the example. This implementation is a toy implementation used to illustrate the approach. It must be seen as a template calling for enrichments and tailorization.

A simple example

The simple example relies on a preexisting session file and on the libOpenStageControl library. Both are given in the zip file.

Loading the score exemple1.asco.txt in your standard patch (for instance, the Antescofo patch provided with the turorial) and starting the score, you have a window with predefined widgets that appear. The following screenshot give you the appearance of this window, when you select the first tab (by default) or the second one.

the interface of the example

We will explain in detail the implementation of the library after presenting the code of the example. The example file starts by including the library.

@insert "libOpenStageControl.asco.txt"

Then a minimal behavior is defined to answer the OSC messages emitted by the widget during their interactions. We handle only two widgets identified by "button1 and fader_1. The reception of OSC messages with these headers must trigger a function or a process. Here we use respectively functions @default_reaction and @fader. The behaviors are collected into a MAP:

@fun_def default_reaction($stage, $args) { print ($args[0]) receives $args }

@fun_def fader($stage, $args)
{
    print ($args[0]) receives $args
    $stage.set("xy_1", [$args[1], $args[1]])
    $stage.set("text_1", "the fader changes the position of the xy_1 point "+$args[1])
}

$reaction := MAP {
    "/button1" -> @default_reaction,
    "/fader_1" -> @fader
}

The session configuration file which defines the user interface is located in the same directory as the Antescofo score and we compute its path:

$path :=  @current_load_directory() + "sessionExemple1.json"

We have now all the information to instantiate an OpenStageControl object (defined in the include file):

$stage := obj::OpenStageControl(
    session_path = $path,
    reaction = $reaction
)

We wait a little bit to be sure that the server is up and ready to answer. By testing the object, we can check that the object is still alive. However, notice that the link between the object and the launched server are loose: the server can be shutdown (manually) and the Antescofo object will not reflect that. Similarly, the Antescofo object can be killed and this has no implication on the server.

5s
if ($stage) { print (""+$stage) still alive }
else { print (""+$stage) is done }

Now we can interact with the client launched by the server. We first plot a curve in a plot widget and we plot the result of a curve on a visualizer widget.

$stage.set("plot_1", [0.33, 0.1, 0.3, 0.99, 0.4, 0.1, 0.5, 1])

group {
    Curve
    @grain 0.02 s
    @action { $stage.set("visualizer_1", $x) }
    {
        $x {
                     {0} "sine_in_out" 
                 5 s {1} "bounce_in_out"
                10 s {0} "exp"
                 4 s {1} 
           }
    } 
    +=> print "curve done"
}

You can experiment some interactions:

  • If you click on the button1 widget, you will see that Antescofo reacts by printing on the console a message, as specified by the @default_reaction behavior.

  • If you click on button2 widget you have a message Widget /button_2 is not handled. This message is emitted in debug mode. In non debug mode, the interaction is simply ignored.

  • Click on the second tab labeled Second Tab. Move the slider: the dot in the xy area (the lower left square area) move accordingly. This is because on the reception of an OSC message from fader_1 Antescofo react by sending a new value to the xy area.

It is possible to add new reactions at anytime by calling the add_reaction method. Here we add two reaction to handle the text area and the range widget that are on the second tab of the interface.

@fun_def input2($stage, $args)
{
    print ($args[0]) receives $args
    $stage.set("text_1", "input: "+$args[1])
}
$stage.add_reaction("/input_2", @input2)


@proc_def rangeP($stage, $args)
{
    @local $x0 := $stage.$x, $y0 := $stage.$y, $xx, $yy

    abort rangePcurve

    Curve rangePcurve
    @grain 0.1 s
    @action { 
        $stage.set("xy_2", [$xx, $yy])
        $stage.$x := $xx 
        $stage.$y := $yy
    }
    {
        $xx { {$x0} 3s {$y0} 4s {($args[1])} }
        $yy { {$y0} 4s {$x0} 3s {($args[2])} }
    }
}
$stage.add_reaction("/range_1", ::rangeP) 

The second reaction as a process: the reaction to a message takes time because we move the dot of the xy widget along an arbitrary curve. The current position of the dot is recorded on the $x and $y slot of the object. A curve is used to send messages to the widget to update the dot position and the $x and $y slots. Before making the animation, we abort other curves taht may have been launched by previious interaction and that have are not yet finished.

The libOpenStageControl library

The library defines an object which materializes the interactions with the server and the client. The object defined here is only a toy example and must be enriched to face real use case. However it gives a good idea of the architecture that ca be developped to interact with Open Stage Control.

The construction of an obj::OpenStageControl has a lot of parameters but each of one has a sensible default value and most of the time it can be omitted in the object instantiation. The use of nammed parameters also ease further instantiation.

@obj_def OpenStageControl (
    $launch_stage = true,
    $remote_machine = "localhost",
    $osc_in = 44322,
    $osc_out = 44301,
    $http_server_port = 8080,
    $session_path = "",
    $reaction = MAP{},
    $debug = true
)

The parameters have the following meaning:

  • $launch_stage is a flag used to ask Antescofo to launch the server. The server is launched as a command line, assuming that the app is installed in /Application.
    If the flag is false, Antescofo nevertheless tries to listen and send messages to a server that has been started elsewhere.

  • $remote_machine is the IP address of the machine where the server is supposed to run. By default, this is localhost, that is, the machine where antescofo is running. If the server is launched by Antescofo, it can be only the local host. Local host is also denoted by the IP address 127.0.0.1
    This does not prevent to have a client running on another machine, but then it must be on a web page.

  • $osc_in is the port number Antescofo is listening to. This port number must be specified to the server as the default target (through the send option in the launcher) when editing the session, so each widget will send its notifications to Antescofo. A specific target can be specified for each widget, but defining a default target spare the burden to define explicitly a receiver for each interaction device.

  • $osc_out defines the port on which the server listens to OSC messages in order to forward them to the widgets. Together with $remote_machine it makes possible to send OSC messages to controil a session.

  • $http_server_port its the port option used by the server to respond to http client requests. In other word, to open the session in a browser, the url to use is defined by:
           http:// $remote_machine : $http_server_port.

  • $session_path is the path (on machine $remote_machine) of the session file. The session file is a Json file produced by the server to save the result of the edition of the session. To save a session file, go to the client menu (the tree vertical dots on the top left banner) and select the session submenu.

  • $reaction is a map that associates the identifier of a widget with a function or a process to call when receiving an OSC message from this widget. The widget has an identifier. When created, this identifier is by default of the form xxx_n where xxxis the type of the widget and n a unique number. The widget notifies its interaction by sending OSC messages with address /xxx_n (note the / as a first character).

  • $debug select the debug mode where all received messages are logged on the console. A receiver printmust be defined in the patch (for example forwarding the arguments to the console).

These parameters are linked to parameters that can be specified through command line arguments or on the launcher interface:

Open Sound Control launcher

There is few slots defined for this object: $x and $y are used to record the current position of the xy area used in the first tab of the interface; $cmd is used to build and record the command used to launch the server ; a d$recv_msg wil be used to handle incoming messages.

```antescofo { @local $cmd := "", $x := 0, $y := 0, $recv_msg

@init 
{
    if ($debug)
    {
        print "OpenStageControl:", \
            "    launch_stage =" $launch_stage, \
            "    remote_machine =" $remote_machine, \
            "    osc_in = " $osc_in, \
            "    osc_out = " $osc_out, \
            "    http_server_port = " $http_server_port, \
            "    session_path = " $session_path, \
            "    debug = " $debug, \
            ("    reaction = " + $reaction)
    }

At instantiation, the `:::antescofo obj::OpenStageControl` object defines an [OSC receiver]( /Reference/atomic_osc#oscreceive) and an [OSC sender](/Reference/atomic_osc#oscsend).antescofo oscrecv recvStage $osc_in * $recv_msg oscsend sendStage @global $remote_machine : $osc_out ```

If the server must be launched by Antescofo, then we build the command line and we execute it using the function @system. When the server is running, we start the predefined session. Server and clienst can be operated remotely using a set of predefined OSC message.

        if ($launch_stage)
        {
            $remote_machine := "localhost"

            $cmd := [
                "/Applications/open-stage-control.app/Contents/MacOS/open-stage-control",
                "-p", $http_server_port,
                "-s", $remote_machine + ":" + $osc_in,
                "-o", $osc_out
            ]
            if (! $session_path.empty()) 
            { 
                $cmd.push_back("-l")
                $cmd.push_back($session_path)
            }
            if ($debug) 
            { 
                $cmd.push_back("-d") 
                print launch $cmd
            }

            $ret := @system($cmd)
            if (!$ret)
            {
                print "Launch Open Stage Control failed"
                abort $THISOBJ
            }

            1s // we wait to be sure that the server is up and listening
            if (! $session_path.empty())
            { sendStage "/SESSION/OPEN" $session_path }
        }
    }

When the object is killed, we display a warning recalling that the server must be killed too.

    @abort
    {
        print "Shutdown Open Stage Control: don't forget to kill the server!"
    }

The object watches the local variable $recv_msg which is updated on the reception of an OSC message. When such a message is received, we can look into the reaction map if there is a key that corresponds to address of the received OSC message (the address of the OSC message coming from a widget, is the identifier of the widget with a leading /.

    @whenever ($recv_msg)
    {
        @local $ok := @is_defined($reaction, $recv_msg[0])

        if ($debug)
        {
            print receive ($recv_msg.size()) "arguments" UI $recv_msg
            forall $i in ($recv_msg.size())
            { print "arg " $i " is " ($recv_msg[$i].type_of()) }

            if (! $ok)
            { print "Widget " ($recv_msg[0]) " is not handled"}
        }

        if ($ok) { $reaction($recv_msg[0])($THISOBJ, $recv_msg)}
    }

Here are some example of additional methods for the convenience of the object user:

    @fun_def send($op, $widget, $args) { sendStage $op $widget $args }

    @fun_def set($widget, $args) { sendStage "/SET" $widget $args }

    @fun_def load_reaction($r) { $reaction := $r }

    @fun_def add_reaction($widget, $action) { 
        $reaction.add_pair($widget, $action) 
        print ("nouvelle reaction: " + $reaction)
    }
}



  1. The software is open source and there is a bug tracker