O2 2.0
A communication protocol for interactive music and media applications.
O2

Introduction

This documentation is divided into modules. Each module describes a different area of functionality: Basics, Return Codes, Low-Level Message Send, and Low-Level Message Parsing.

on Building the O2 Library

O2 uses CMake, which can create a Linux Makefile, Xcode project, or Visual Studio solution. The basic procedure is run CMake (ccmake on Linux) to create the Makefile of project file, then build using make or your IDE. The main options for CMake are BUILD_MIDI_EXAMPLE: builds an example that depends on PortMidi (disable this option unless you need it), BUILD_STATIC_LIB: builds a static O2 library (recommended), BUILD_TESTS: builds a suite of tests (useful for O2 developers), and BUILD_TESTS_WITH_LIBLO: includes some tests that depends on the OSC library liblo (also useful for O2 developers).

To use O2 in your application, normally you should only need to include the O2 static library o2_static.lib into your project, make sure the path to o2.h is on your headers search path, and maybe put the path to o2_static.lib on your libraries search path.

O2 depends on some libraries. On Windows, if your application includes O2, you will need to link to winmm.lib and ws2_32.lib. On OS X, you will need to link to the CoreAudio framework. On Linux, you will need to link to asound (you may have to install a developer package to get this), pthread, and m (the math library).

Overview

O2 is a communication protocol for interactive music and media applications. O2 is inspired by Open Sound Control (OSC) and uses similar means to form addresses, specify types, and encode messages.

However, in addition to providing message delivery, O2 offers a discovery mechanism where processes automatically discover and connect to other processes. Each process can offer zero or more named "services," which are top-level nodes in a global, tree-structured address space for a distributed O2 application. In O2, services replace the notion of network addresses (e.g. 128.2.100.57) in OSC.

O2 is based on IP (Internet Protocol), but there is a mechanism that allows an O2 process to serve as a bridge to other protocols including WebSockets, shared memory, and MQTT. OSC is also supported: An O2 service can be delegated to an OSC server address, and O2 can offer an OSC server that forwards to an O2 service.

O2 addresses begin with the service name. Thus, a complete O2 address would be written simply as "/synth/filterFtoff," where "synth" is the service name.

Furthermore, O2 implements a clock synchronization protocol. A single process is designated as the "reference," and other processes automatically synchronize their local clocks to the reference. All O2 messages are timestamped. Messages are delivered immediately, but their designated operations are invoked according to the timestamp. A timestamp of zero (0.0) means deliver the message immediately. Messages with non-zero timestamps are only deliverable after both the sender and receiver have synchronized clocks.

A service is created using the functions:

o2_service_new("service_name")

and

o2_method_new("address," "types," handler, user_data, coerce, parse)

, where o2_method_new is called to install a handler for each node, and each "address" includes the service name as the first node.

Some major components and concepts of O2 are the following:

  • Ensemble - a collection of collaborating processes are called an "ensemble." Ensembles are named by a simple ASCII string. In O2, all components belong to an ensemble, and O2 supports communication only within an ensemble. This allows multiple independent ensembles to co-exist and share the same local area network.
  • Host - (conventional definition) a host is a computer (virtual or real) that may host multiple processes. Essentially, a Host is equivalent to an IP address.
  • Process - (conventional definition) an address space and one or more threads. O2 assumes that all processing is performed by a single thread (O2 is not reentrant). A process can offer one or more O2 services and can serve as a client of O2 services in the same or other processes. Each process using O2 has one directory of services shared by all O2 activity in that process (thus, this O2 implementation can be thought of as a "singleton" object). The single O2 instance in a process belongs to one and only one ensemble. O2 does not support communication between ensembles. It is the responsibility of an O2 process to call o2_poll frequently, giving O2 the opportunity to receive and process messages as well as run background activities for discovery and clock synchronization. o2_poll is non-blocking.
  • Service - an O2 service is a named server that receives and acts upon O2 messages. A service is addressed by name. Multiple services can exist within one process. A service does not imply a (new) thread and all O2 messages are delivered sequentially from the single thread that calls o2_poll. Service names begin with a letter with the exception of "_o2" which denotes the local process, "_cs", which denotes the reference clock, and @public:internal:port strings that denote a remote process.
  • Message - an O2 message, similar to an OSC message, contains an address pattern representing a function, a type string and a set of values representing parameters. Messages are delivered to and handled by services. If there are multiple services with the same name, the service with the highest IP address and port number (lexicographically) gets the message. However, the tap mechanism can be used to achieve fan out from a single service (the tappee) to multiple services (tappers).
  • Address Pattern - O2 messages use URL-like addresses, as does OSC, but the top-level node in the hierarchical address space is the service name. Thus, to send a message to the "note" node of the "synth" service, the address pattern might be "/synth/note." The OSC pattern specification language is used unless the first character is "!", e.g. "!synth/note" denotes the same address as "/synth/note" except that O2 can assume that there are no pattern characters such as "*" or "[". The first part of an address is the service name; pattern characters are not allowed in the service name, so the service name must match exactly. A message may be delivered to the service itself, e.g. with address pattern "/synth". To receive such a message, the service must have a single handler that handles every message to the service (see o2_method_new).
  • Scheduler - O2 implements two schedulers for timed message delivery. Schedulers are served by the same o2_poll call that runs other O2 activity. The o2_gtsched schedules according to the ensemble's reference clock time, but since this depends on clock synchronization, nothing can be scheduled until clock synchronization is achieved (typically within a few seconds of starting the process that provides the reference clock). For local-only services, it is possible to use the o2_ltsched scheduler (not with o2_send, but by explicitly constructing messages and scheduling them with o2_schedule_msg. In any case, scheduling is useful within a service for any kind of timed activity.

Messages

O2 uses O2 messages for some internal functions and also to communicate status information with O2 processes. Since messages are dropped if there is no handler, O2 processes need not set up handlers for all information.

In the following descriptions, if the service name is @public:internal:port it means the IP address and port number are used to construct a string, e.g. "@4a6dfb76:c0a801a6:fc1d" might be the actual service name.

Messages

/_o2/dy "sissiii" ensemble_name version public_ip internal_ip tcp_port udp_port dy - this message is normally sent to the discovery port, but it can also be sent as a result of calling o2_hub and providing an O2 process address. Processes must exchange discovery messages to be connected. The version number encodes the O2 version number of the sender as ((<major-vers> * 256) + <minor-vers>) * 256 + <patch-vers>, where <major-vers>, <minor-vers> and <patch-vers> follow the O2 semantic versioning scheme, e.g. version 2.3.4 would have version number 0x020304. Thus, each component of the 3-part version number ranges from 0 through 255. Connections are completed only if the major version numbers match. The dy parameter is O2_DY_INFO, O2_DY_HUB, O2_DY_REPLY, O2_DY_CALLBACK, O2_DY_CONNECT, or O2_DY_O2LITE, depending on the expected response.

/_o2/hub "" - requests the receiver to become the hub for the sender

/_o2/cs/cs "" - announces when clock sync is obtained.

/_o2/ds "" - this message invokes the sending of discovery messages. It is used with a timestamp to schedule periodic discovery message sending.

`/_cs/get "is" serial-no reply-to - send the time. The reply-to parameter is the full address for the reply. The reply contains the type string "it" and the parameters are the serial-no and the current time.

/_o2/sv "ssbbsi..." process-name service-name add-flag service-flag* tapper-or-properties send_mode... - reports service creation or deletion. Each service is described by name, true, and either true followed by a properties string and zero or false followed by the tapper name and tapper send_mode. If a service is deleted, then false is sent rather than true, and if this is not a tap, the properties string is empty. The "..." notation here indicates that there can be any number of services described in this message, each service consisting of another "sbbsi" (string, Boolean, Boolean, string, int32) sequence. Properties strings are sent with escaped values, a leading ';' and a trailing ';'. This is the first message sent when a process-to-process connection is made.

Messages

/_o2/cs/rt "s" reply-to - A process can send this message to request round trip (to the clock reference) information; reply-to is the full address of the reply. The reply message has the type string "sff" and the parameters are the process name (:internal:port), the mean round-trip time, and the minimum round-trip time.

/_o2/cs/ps "" - this message invokes the sending of the /_cs/get message to request the time as part of the clock synchronization protocol.

/_o2/si "siss" service_name status process-name properties - Whenever an active service status changes or service properties change, this message is sent to the local process. Note that when a local service is created, an internal /sv message is sent to all other processes, and when that process's services table is updated, if the service is or becomes active, the change is reported locally to the application by sending this /_o2/si message. Normally, you should not add handlers or use the _o2 service, but in this case, an application is expected to add a custom handler to receive status updates. See o2_status for a list of status values. O2_UNKNOWN is reported when the current provider of a service is removed. This may be followed immediately by another message to /_o2/si if another provider offers the service. The service _o2 is created before you can install a handler for /_o2/si, so you will not receive an si message when _o2 is created. If the service name begins with '@', it represents a remote process and the name has the form:internal:port, e.g. @c0a35801:80e8a1a5:d67e. There is also a @public:internal:port service representing the local process, but the local process is also represented by _o2, so the local @public:internal:port service is never reported in an /_o2/si message. Instead, the string "_o2" indicates that process is the local one. You can get the local @public:internal:port string by calling o2_get_addresses or o2_get_proc_name. The properties string contains the service properties in the form "attr1:value1;attr2:value2;" with no leading ";". If there are no properties, the string is empty. Values are escaped: within a value, the characters ":;\" are preceded by "\" since ":" and ";" are separator characters. Attribute names are alphanumeric.