O2 2.0
A communication protocol for interactive music and media applications.
|
Classes | |
struct | O2msg_data |
data part of an O2 message More... | |
struct | O2message |
an O2 message container More... | |
struct | O2blob |
The structure for binary large object. More... | |
union | O2arg |
union of all O2 parameter types More... | |
Macros | |
#define | O2MEM_ALIGN 8 |
get the type string from o2_msg_data_ptr More... | |
#define | O2MEM_ALIGNUP(s) ( ((s)+(O2MEM_ALIGN-1)) & ~(O2MEM_ALIGN-1) ) |
#define | O2MEM_BIT32_ALIGN_PTR(p) ((char *) (((size_t) (p)) & ~3)) |
#define | O2_MSG_DATA_END(data) (PTR(&(data)->misc) + (data)->length) |
#define | o2_msg_data_types(data) (o2_next_o2string((data)->address) + 1) |
#define | o2_msg_types(msg) o2_msg_data_types(&msg->data) |
#define | o2_msg_data_params(types) o2_next_o2string((const char *) ((intptr_t) (types) & ~3)) |
#define | O2_MSG_PAYLOAD(msg) PTR(&(msg)->data.misc) |
The address of the actual message, not including the length field: | |
#define | o2_send(path, time, ...) |
Construct and send O2 message with best effort protocol. More... | |
#define | o2_send_cmd(path, time, ...) |
Construct and send an O2 message reliably. More... | |
#define | streql(a, b) (strcmp((a), (b)) == 0) |
#define | O2_MAX_PROCNAME_LEN 32 |
#define | O2_MAX_NAME_LEN 63 |
#define | O2_MALLOC(x) o2_dbg_malloc(x, __FILE__, __LINE__) |
#define | O2_MALLOCNT(n, typ) ((typ *) O2_MALLOC((n) * sizeof(typ))) |
#define | O2_MALLOCT(typ) O2_MALLOCNT(1, typ) |
#define | O2_FREE(x) o2_dbg_free(x, __FILE__, __LINE__) |
#define | O2_CALLOC(n, s) o2_dbg_calloc(n, s, __FILE__, __LINE__) |
#define | O2_CALLOCNT(n, typ) ((typ *) O2_CALLOC(n, sizeof(typ))) |
#define | O2_CALLOCT(typ) O2_CALLOCNT(1, typ) |
#define | O2MEM_DEBUG 1 |
Typedefs | |
typedef double | O2time |
O2 timestamps are doubles representing seconds since the approximate start time of the ensemble. | |
typedef struct O2msg_data | O2msg_data |
data part of an O2 message More... | |
typedef struct O2msg_data * | o2_msg_data_ptr |
typedef struct O2message | O2message |
an O2 message container More... | |
typedef struct O2message * | O2message_ptr |
typedef struct O2blob | O2blob |
The structure for binary large object. More... | |
typedef struct O2blob * | O2blob_ptr |
typedef enum O2type * | O2type_ptr |
typedef union O2arg * | O2arg_ptr |
typedef void(* | O2method_handler) (const o2_msg_data_ptr msg, const char *types, O2arg_ptr *argv, int argc, const void *user_data) |
callback function to receive an O2 message More... | |
typedef O2time(* | o2_time_callback) (void *rock) |
signature for callback that defines the reference clock More... | |
Enumerations | |
enum | O2type { O2_INT32 = 'i' , O2_FLOAT = 'f' , O2_STRING = 's' , O2_BLOB = 'b' , O2_ARRAY_START = '[' , O2_ARRAY_END = ']' , O2_INT64 = 'h' , O2_TIME = 't' , O2_DOUBLE = 'd' , O2_SYMBOL = 'S' , O2_CHAR = 'c' , O2_MIDI = 'm' , O2_TRUE = 'T' , O2_FALSE = 'F' , O2_NIL = 'N' , O2_INFINITUM = 'I' , O2_BOOL = 'B' , O2_VECTOR = 'v' } |
An enumeration of the O2 message types. More... | |
enum | O2tap_send_mode { TAP_KEEP , TAP_RELIABLE , TAP_BEST_EFFORT } |
Functions | |
O2err | o2_internet_enable (bool enable) |
Disable Internet connections. More... | |
O2err | o2_network_enable (bool enable) |
Disable external nework connections. More... | |
const char * | o2_next_o2string (const char *str) |
void | o2_message_print (O2message_ptr msg) |
print an O2 message to stdout More... | |
void | o2_msg_data_print (o2_msg_data_ptr msg) |
print a message from msg_data_ptr to stdout More... | |
O2err | o2_initialize (const char *ensemble_name) |
Start O2. More... | |
int | o2_version (char *version) |
get O2 version number More... | |
int | o2_memory (void *((*malloc)(size_t size)), void((*free)(void *)), char *first_chunk, int64_t size, bool mallocp) |
Tell O2 how to allocate/free memory. More... | |
O2time | o2_set_discovery_period (O2time period) |
Set discovery period. More... | |
O2err | o2_hub (int version, const char *public_ip, const char *internal_ip, int tcp_port, int udp_port) |
Connect to a hub. More... | |
O2err | o2_get_addresses (const char **public_ip, const char **internal_ip, int *port) |
Get IP address and TCP connection port number. More... | |
const char * | o2_get_proc_name (void) |
Get Public:Local:Port service name string. More... | |
int | o2_parse_name (const char *name, char *public_ip, char *internal_ip, int *port) |
Parse Public:Local:Port string and extract fields. More... | |
O2err | o2_service_new (const char *service_name) |
Add a service to the current process. More... | |
O2err | o2_services_list (void) |
list known services and taps More... | |
O2err | o2_services_list_free (void) |
free the list of known services and taps More... | |
const char * | o2_service_name (int i) |
get a service name from a saved list of services More... | |
int | o2_service_type (int i) |
get a type from a saved list of services More... | |
const char * | o2_service_process (int i) |
get a process name from a saved list of services More... | |
const char * | o2_service_tapper (int i) |
get a tapper name from a saved list of services More... | |
const char * | o2_service_properties (int i) |
get the properties string from a saved list of services More... | |
const char * | o2_service_getprop (int i, const char *attr) |
get a property value from a saved list of services More... | |
int | o2_service_search (int i, const char *attr, const char *value) |
find a service matching attribute/value pair More... | |
O2err | o2_service_set_property (const char *service, const char *attr, const char *value) |
set an attribute and value property for a service More... | |
O2err | o2_service_property_free (const char *service, const char *attr) |
remove an attribute and value property from a service More... | |
O2err | o2_tap (const char *tappee, const char *tapper, O2tap_send_mode send_mode) |
install tap to copy messages from one service to another More... | |
O2err | o2_untap (const char *tappee, const char *tapper) |
remove tap from service More... | |
O2err | o2_service_free (const char *service_name) |
Remove a local service. More... | |
O2err | o2_method_new (const char *path, const char *typespec, O2method_handler h, const void *user_data, bool coerce, bool parse) |
Add a handler for an address. More... | |
O2err | o2_method_free (const char *path) |
remove a path – remove a path and associated handler More... | |
void | o2_message_drop_warning (const char *warn, o2_msg_data_ptr msg) |
Default dropped message alert. More... | |
void | o2_drop_message (const char *warn, bool free_the_msg) |
Tell world that a message was dropped. More... | |
void | o2_message_warnings (void(*warning)(const char *warn, o2_msg_data_ptr msg)) |
Enable/Disable warnings for dropped messages. More... | |
O2err | o2_poll (void) |
Process current O2 messages. More... | |
int | o2_run (int rate) |
Run O2. More... | |
int | o2_status (const char *service) |
Check the status of the service. More... | |
const char * | o2_status_to_string (int status) |
retrieve text version of an O2status More... | |
O2err | o2_can_send (const char *service) |
Test if send_cmd will block. More... | |
int | o2_roundtrip (double *mean, double *min) |
Get network round-trip information. More... | |
int | o2_clock_set (o2_time_callback gettime, void *rock) |
Provide a time reference to O2. More... | |
O2err | o2_message_send (O2message_ptr msg) |
Send an O2 message. (See also macros o2_send and o2_send_cmd). More... | |
O2time | o2_time_get (void) |
Get the estimated synchronized global O2 time. More... | |
O2time | o2_local_time (void) |
Get the real time using the local O2 clock. More... | |
O2err | o2_finish (void) |
release the memory and shut down O2. More... | |
O2err | o2_schedule_msg (O2sched_ptr scheduler, O2message_ptr msg) |
void | o2_sleep (int n) |
Suspend for n milliseconds. More... | |
unsigned int | o2_hex_to_int (const char *hex) |
Convert hex string to integer. More... | |
void | o2_hex_to_dot (const char *hex, char *dot) |
Convert from hex format to dot format IP address. More... | |
void ((*o2_free_ptr)(void *)) | |
void * | o2_dbg_malloc (size_t size, const char *file, int line) |
allocate memory More... | |
void | o2_dbg_free (void *obj, const char *file, int line) |
free memory allocated by #O2_MALLOC | |
void * | o2_dbg_calloc (size_t n, size_t s, const char *file, int line) |
allocate and zero memory (see #O2_MALLOC) More... | |
void | o2_mem_check (void *ptr) |
Variables | |
O2arg_ptr | o2_got_start_array |
O2arg_ptr | o2_got_end_array |
bool | o2_stop_flag |
set this flag to stop o2_run More... | |
const char * | o2_ensemble_name |
bool | o2_clock_is_synchronized |
A variable indicating that the clock is the reference or is synchronized to the reference. | |
O2sched | o2_gtsched |
Scheduler that schedules according to global (reference) clock time. More... | |
O2sched | o2_ltsched |
Scheduler that schedules according to local clock time. More... | |
O2sched_ptr | o2_active_sched |
Current scheduler. More... | |
void *(* | o2_malloc_ptr )(size_t size)) |
#define o2_send | ( | path, | |
time, | |||
... | |||
) |
Construct and send O2 message with best effort protocol.
Normally, this constructs and sends an O2 message via UDP. If the destination service is reached via some other network protocol (e.g. Bluetooth), the message is delivered in the lowest latency protocol available, with no guaranteed delivery.
path | an address pattern |
time | when to dispatch the message, 0 means right now. In any case, the message is sent to the receiving service as soon as possible. If the message arrives early, it will be held at the service and dispatched as soon as possible after the indicated time. |
typestring | the type string for the message. Each character indicates one data item. Allowed type characters are those in "ifsbhtdScmTFNIB". Threee O2 type codes: "[]v" are not supported. (if needed, create a message and use the appropriate o2_add_... function.) |
... | the data of the message. There is one parameter for each character in the typestring. |
#define o2_send_cmd | ( | path, | |
time, | |||
... | |||
) |
Construct and send an O2 message reliably.
Normally, this constructs and sends an O2 message via TCP. If the destination service is reached via some other network protocol (e.g. Bluetooth), the message is delivered using the most reliable protocol available. (Thus, this call is considered a "hint" rather than an absolute requirement.)
If the send would block, the message is held until the socket unblocks, allowing O2 to send the message. In this state, where one message is waiting to be sent, o2_can_send will return O2_BLOCKED. In this state, o2_send_cmd will block until the pending message can be sent. Then o2_send_cmd will try again, possibly re-entering the O2_BLOCKED state with the new message.
path | an address pattern |
time | when to dispatch the message, 0 means right now. In any case, the message is sent to the receiving service as soon as possible. If the message arrives early, it will be held at the service and dispatched as soon as possible after the indicated time. |
typestring | the type string for the message. Each character indicates one data item. Type codes are defined by O2type. |
... | the data of the message. There is one parameter for each character in the typestring. |
#define O2MEM_ALIGN 8 |
get the type string from o2_msg_data_ptr
Type strings begin with the comma (",") character, which is skipped
typedef O2time(* o2_time_callback) (void *rock) |
signature for callback that defines the reference clock
See o2_clock_set for details.
The structure for binary large object.
A blob can be passed in an O2 message using the 'b' type. Created by calls to o2_blob_new.
an O2 message container
Note: This struct represents an O2 message that is stored on the heap. O2message is an alias for O2netmsg. At the o2n (network) abstraction, there is no O2msg_data type.
Note that O2messages are on the heap and can be allocated, scheduled, sent, and freed. In contrast, O2msg_data structures are contained within O2messages and are passed to method handlers, but cannot be allocated, scheduled, sent, or freed. They are always the data field of a containing O2message.
typedef void(* O2method_handler) (const o2_msg_data_ptr msg, const char *types, O2arg_ptr *argv, int argc, const void *user_data) |
callback function to receive an O2 message
msg | The full message in host byte order. |
types | If you set a type string in your method creation call, then this type string is provided here. If you did not specify a string, types will be the type string from the message (without the initial ','). If parse_args and coerce_flag were set in the method creation call, types will match the types in argv, but not necessarily the type string or types in msg. |
argv | An array of O2arg types containing the values, e.g. if the first argument of the incoming message is of type 'f' then the value will be found in argv[0]->f. (If parse_args was not set in the method creation call, argv will be NULL.) For vectors, specified in types by the sequence "vi", "vh", "vf", or "vd", there will be one pointer in argv pointing to a vector description (the v field in O2arg). For arrays, there are no pointers corresponding to '[' or ']' in the types string; but there is one pointer in argv for each array element. |
argc | The number of arguments received. (This is valid even if parse_args was not set in the method creation call.) This is the length of argv. Vectors count as one, array elements count as one each, and arrays themselves are not represented. For example, an empty array ("[]") in the type string adds nothing to the argc count or argv vector. |
user_data | This contains the user_data value passed in the call to the method creation call. |
typedef struct O2msg_data O2msg_data |
data part of an O2 message
This data type is used to pass o2 message data to message handlers. It appears many other times in the code. You should NEVER allocate or free an O2msg_data struct. Instead, create a message using o2_send_start, o2_add_*()
, and o2_message_finish to get an O2message_ptr. Within the O2message, the data field is an O2msg_data structure. We would use O2message everywhere instead of O2msg_data, but bundles can contain multiple O2msg_data structures without the extra baggage contained in an O2message.
enum O2type |
An enumeration of the O2 message types.
O2err o2_can_send | ( | const char * | service | ) |
Test if send_cmd will block.
service | the name of the service. |
If a process is streaming data to another and the TCP buffers become full, the sender will block. Normally, blocking is short term, but if the receiver is not reading, the sender can block indefinitely. This can lead to deadlock if processes block trying to send to each other. If necessary for responsiveness or deadlock-avoidance, the sender should call o2_can_send before calling o2_send_cmd. If O2_BLOCKED is returned, the caller should defer the call to o2_send_cmd but continue calling o2_poll, and at some point in the future, provided the receiver is receiving, o2_can_send should return O2_SUCCESS.
This works if service
forwards messages locally to OSC via TCP. However, if service
is provided by another process, and that process forwards messages to an OSC server via TCP, then the return value indicates whether the first hop to the remote process will block. Once the message arrives at the remote process, the hop to the OSC server might be blocked, in which case the remote process will block. This could create any number of problems. It is safer to send to the service using o2_send (UDP). In that case, the remote process will drop the message rather than block. An even better option might be to send directly to the OSC server by creating a local service for it (see o2_osc_delegate). In order for two processes to connect to the same OSC server, it is necessary to use different service names: one for each sending process. Otherwise, one process will take priority and all messages will will flow through that process. If it is not possible for all processes to connect directly to the OSC server, and if blocking must be avoided, the most general solution is to create an O2 service to receive messages and resend them to OSC. The receiver can then detect blocking using o2_can_send and implement any policy or filtering desired.
int o2_clock_set | ( | o2_time_callback | gettime, |
void * | rock | ||
) |
Provide a time reference to O2.
Exactly one process per O2 ensemble should provide a reference clock. All other processes synchronize to the reference. To become the reference, call o2_clock_set.
The time reported by the gettime function will be offset to match the current local time so that local time continues to increase smoothly. You cannot force O2 time to match an external absolute time, but once o2_clock_set is called, the difference between the time reference and O2's local time (as reported by o2_local_time) will be fixed.
gettime | function to get the time in units of seconds. The reference may be operating system time, audio system time, MIDI system time, or any other time source. The times returned by this function must be non-decreasing and must increase by one second per second of real time to close approximation. The value may be NULL, in which case a default time reference will be used. |
@parm rock an arbitrary value that is passed to the gettime function. This may be need to provide context. Use NULL if no context is required.
void * o2_dbg_calloc | ( | size_t | n, |
size_t | s, | ||
const char * | file, | ||
int | line | ||
) |
allocate and zero memory (see #O2_MALLOC)
Similar to calloc, but this uses the malloc and free functions provided to O2 through a call to o2_memory().
[in] | n | The number of objects to allocate. |
[in] | size | The size of each object in bytes. |
void * o2_dbg_malloc | ( | size_t | size, |
const char * | file, | ||
int | line | ||
) |
allocate memory
O2 allows you to provide custom heap implementations to avoid priority inversion or other real-time problems. Normally, you should not need to explicitly allocate memory since O2 functions are provided to allocate, construct, and deallocate messages, but if you need to allocate memory, especially in an O2 message handler callback, i.e. within the sphere of O2 execution, you should use #O2_MALLOC, #O2_FREE, and #O2_CALLOC and their variants.
The O2lite library shares some code with O2 but not memory allocation. To simplify things, you can just define O2_MALLOC, e.g. to be malloc, and define O2_FREE, e.g. to be free, and the logic here will provide implementations of O2_MALLOCT, O2_MALLOCNT, etc.
void o2_drop_message | ( | const char * | warn, |
bool | free_the_msg | ||
) |
Tell world that a message was dropped.
warn | A human-readable description of the cause. |
free_the_msg | if true, frees the message |
Applications and libraries can call this function to report dropped messages in the same manner as O2. The default behavior is to call o2_message_drop_warning or another handler installed by a previous call to o2_message_warnings. This function assumes the message is at the head of the list o2_ctx->msgs, so you can access it wtih o2_current_message() and aquire ownership with o2_postpone_delivery().
O2err o2_finish | ( | void | ) |
release the memory and shut down O2.
Close all sockets, free all memory, and restore critical variables so that O2 behaves as if it was never initialized.
Note that o2_finish will close websockets immediately without performing the standard websocket close protocol. The recommended clean shutdown with o2lite running over websockets is: If the O2 host is to shut down, send a message from the browser to the O2 host (see test/websockhost.cpp
and its stop_handler
function for example.) Then call o2ws_finish()
in the browser. If the O2 host is shutting down, have it call o2_poll() for a 100 ms or so, giving O2 a chance to respond to the incoming websocket CLOSE command. This will complete the shutdown on the browser side, avoiding an exception there. Then, the O2 host can call o2_finish to close all sockets and free resources.
O2err o2_get_addresses | ( | const char ** | public_ip, |
const char ** | internal_ip, | ||
int * | port | ||
) |
Get IP address and TCP connection port number.
Before calling o2_hub, you need to know the IP address and TCP connection port of another process. This call will retrieve the information, but the mechanism to transfer this information to another O2 process (or all of them) must be implemented outside of O2. (If the local network allows UDP broadcast and all hosts are on the local network, then you do not need this function or o2_hub. Instead, let the discovery protocol exchange process addresses automatically.)
Because O2 must query a STUN server to obtain the public IP address, this function will return O2_FAIL for some time after o2_initialize
is called. Typically, the public IP address will be available in less than 1 second, but the STUN calls will continue for 10 seconds before O2 gives up and concludes that the Internet is unreachable, in which case the public_ip is set to 00000000 and this call returns O2_SUCCESS.
If there is no network at all, the internal ip is 7f000001 (localhost) and O2 will still operate, connecting to other O2 processes on the same host.
public_ip | is a pointer that will be set to either NULL (on failure) or a string of the form "80100a06". The string should not be modified, and the string will be freed by O2 if o2_finish is called. |
internal_ip | is a pointer that will be set to either NULL (on failure) or a string of the form "c0000006". The string should not be modified, and the string will be freed by O2 if o2_finish is called. |
port | will be set to a pointer to the O2 TCP connection port (or NULL on failure). |
const char * o2_get_proc_name | ( | void | ) |
Get Public:Local:Port service name string.
An O2 process is identified by its IP addresses and Port number. An O2 process is a special service name that is automatically created when O2 is initialized. For example, "@6a960032:c0a801b6:55630". By convention, you should always use "_o2" instead. This is an alias for the local process. The full:local:port string for a process is used, for example, in status messages ("/_o2/si") to identify a remote process that is providing the service.
void o2_hex_to_dot | ( | const char * | hex, |
char * | dot | ||
) |
Convert from hex format to dot format IP address.
O2 uses 8 digit hexadecimal notation for IP addresses, mostly internally. To convert to the more conventional "dot" notation, e.g. "127.0.0.1", call o2_hex_to_dot.
hex | is a string containing an 8 character hexadecimal IP address. |
dot | is a memory area of size O2N_IP_LEN or greater where the dot notation is written. |
unsigned int o2_hex_to_int | ( | const char * | hex | ) |
Convert hex string to integer.
hex | a string of hex digits (no minus sign allowed) |
O2err o2_hub | ( | int | version, |
const char * | public_ip, | ||
const char * | internal_ip, | ||
int | tcp_port, | ||
int | udp_port | ||
) |
Connect to a hub.
A "hub" is an O2 process that shares discovery information with other processes. This is an alternate form of discovery that is completely compatible with the broadcast-based discovery protocol, except (1) you do not need broadcast messages to communicate with a hub, (2) you do need the hub's IP address and port number. If the IP and port number can be shared, e.g. through a server or online database with a fixed address, you can work with networks that disallow broadcast, and you can connect across networks (which will not work with O2's normal discovery protocol if broadcast messages are not delivered across networks). To use a hub, you call o2_hub with the hub's IP address and port. All O2 processes are effectively hubs with no clients, and o2_hub simply connects to the hub as a client. The hub will then send discovery messages for all current and future O2 processes that are discovered, either through the normal discovery protocol or by connecting with the o2_hub call.
o2_hub only works if there is an Internet connection, so connecting through MQTT is likely to be a better solution.
After o2_hub is called, discovery broadcasting is stopped, so if o2_hub fails to connect to another process, you will only discover more processes if they initiate the exchange. You can use o2_hub specifically to disable broadcast-based discovery by passing NULL as the public_ip parameter.
You can call o2_hub multiple times, but the call may simply return O2_FAIL if o2_hub is called more than once before a public IP address is obtained. Each call potentially makes a remote process become a hub for this local process. This might result in duplicate messages when new processes join the O2 ensemble, but duplicate messages are ignored.
version | is the version number of of the hub, e.g. 0x20103 for 2.1.3 |
public_ip | the public IP address of the hub or NULL |
internal_ip | the local IP address of the hub |
tcp_port | the port number of the hub's TCP port |
udp_port | the port number of the hub's UDP port |
O2err o2_initialize | ( | const char * | ensemble_name | ) |
Start O2.
If O2 has not been initialized, it is created and intialized. O2 will begin to establish connections to other instances with a matching ensemble name.
ensemble_name | the name of the ensemble. O2 will attempt to discover other processes with a matching ensemble name, ignoring all processes with non-matching names. |
ensemble_name
is NULL. O2err o2_internet_enable | ( | bool | enable | ) |
Disable Internet connections.
Set the default to enable or disable Internet connections to other hosts. This setting can only be changed before O2 is started with o2_initialize() or between calls to o2_finish() and o2_initialize. When o2_initialize() is called and network connections are enabled, O2 will try to obtain a local (internal) IP address. If found, O2 will then try to obtain a public IP address. This may result in a long delay if the Internet cannot be reached. See o2_network_enable for more detail. If Internet connections are disabled, the delay can be avoided and the public IP address is immediately set to 0.0.0.0. Even then, O2 will still attempt to open Internet connections for OSC. OSC connections depend only on network connectivity and ignore the o2_internet_enable setting.
When Internet connections are disabled, O2 processes can still interconnect on the local area network. If an ensemble is expected to run only within the local area network, Internet connections (including possible security threats as well as some overhead in finding the public IP address and setting up MQTT connections) can be blocked using this option.
Internet connections can also be disabled by passing 'I' as a character in the string parameter of o2_debug_flags.
enable | Use true to enable or false to disable Internet connections. |
O2time o2_local_time | ( | void | ) |
Get the real time using the local O2 clock.
int o2_memory | ( | void * | (*malloc)(size_t size), |
void((*free)(void *)) | , | ||
char * | first_chunk, | ||
int64_t | size, | ||
bool | mallocp | ||
) |
Tell O2 how to allocate/free memory.
In many C library implementations, the standard implementation of free()
must lock a data structure. This can lead to priority inversion if O2 runs at an elevated priority. Furthermore, the standard malloc()
and free()
do not run in constant (real) time. To avoid these problems, O2 uses it's own memory allocation by default, or you can provide an alternate heap implementation for O2 by calling this function before calling o2_initialize.
The application should always use #O2_MALLOC, #O2_MALLOCT, #O2_MALLOCNT, #O2_CALLOC, and #O2_CALLOCT functions, which add a layer of debugging support and call either the default O2 malloc()
and free()
functions or the ones provided in this call.
The default configuration corresponds to parameters: NULL, NULL, NULL, 0, true. In other words, O2 will allocate chunks of heap space using the C-library #malloc function as needed.
o2_memory can be called one time before the first call to o2_initialize. The configuration is retained and reused, including #chunk and #size, even if O2 is restarted by calling o2_finish #followed by #o2_intialize.
malloc | a function pointer that behaves like standard malloc() or NULL to use O2's default memory allocator. |
free | a function pointer that behaves like standard free() or NULL to use O2's default memory free function. (The value should be NULL iff #malloc is NULL.) |
first_chunk | if #malloc and #free are NULL, #first_chunk provides a memory area from which O2 can allocate as needed. This value may be NULL. In non-NULL, #first_chunk is owned by O2 until the last call to o2_finish, after which the caller should free it (or not – it is possible to use a large static block of memory rather than the heap). |
size | is the size in bytes of #first_chunk. Specify 0 if #first_chunk is NULL. |
mallocp | if #malloc and #free are NULL, and O2 runs out of memory, either because #first_chunk is NULL to begin with or because it was not big enough to meet the allocation needs of O2, then O2 can be directed to use the C-library #malloc to allocate another chunk of memory by passing #true for #mallocp. |
void o2_message_drop_warning | ( | const char * | warn, |
o2_msg_data_ptr | msg | ||
) |
Default dropped message alert.
warn | A human-readable description of the cause. |
msg | a pointer to the message data to be dropped |
This default parameter for o2_message_warnings prints the warn
string followed by the message to be dropped (if O2_NO_DEBUG is undefined) or the message address and type string (if O2_NO_DEBUG is defined) to stdout. Do not call this function. Use o2_drop_message or #o2_drop_msg_data instead. You can pass this function to o2_message_warnings to restore default warning behavior.
void o2_message_print | ( | O2message_ptr | msg | ) |
print an O2 message to stdout
For debugging, this function will print a human-readable representation of a message and its parameters.
msg | a message to be printed |
O2err o2_message_send | ( | O2message_ptr | msg | ) |
Send an O2 message. (See also macros o2_send and o2_send_cmd).
msg | points to an O2 message. |
After the call, the msg
parameter is "owned" by O2, which will free it. Therefore, do not free msg after calling o2_message_send.
void o2_message_warnings | ( | void(*)(const char *warn, o2_msg_data_ptr msg) | warning | ) |
Enable/Disable warnings for dropped messages.
warning | a function such as o2_message_drop_warning to issue the warning or NULL. |
It can be very annoying when O2 messages are not received due to an error in setting up a message handler with o2_method_new. Therefore, the default behavior is to print a message whenever an O2 message is dropped. (Exceptions: messages addressed to "!_o2/si" but not handled are dropped without warning. This address is described under "API Messages." Undelivered messages when O2 is shut down with o2_finish are silently deleted.) The default warning behavior is to print a warning to stdout, but since that does not always exist, you can call this function to install a custom handler.
Call #o2_message_warnings(NULL) to disable any warnings.
The #warning function, if any, must not free #msg, which is owned and eventually freed by the caller.
No warning is printed if a message is lost by the network or queued for network delivery when the receiver closes the socket or loses its connection. Also, UDP messages have no acknowledgements, so there is no way to warn if a UDP message from O2 is intended for an OSC server that does not even exist. With O2, however, failed or non-existent remote services are detected (eventually), resulting in warnings even for UDP messages.
O2err o2_method_free | ( | const char * | path | ) |
remove a path – remove a path and associated handler
To remove a handler, call this function to remove it from the handler lookup structure. You can also remove a subtree of handlers, e.g. if path is /W/X, it will remove any handlers on paths starting with /W/X, e.g. /W/X/Y and /W/X/Z.
path | The path of the method |
O2err o2_method_new | ( | const char * | path, |
const char * | typespec, | ||
O2method_handler | h, | ||
const void * | user_data, | ||
bool | coerce, | ||
bool | parse | ||
) |
Add a handler for an address.
path | the address including the service name. |
typespec | the types of parameters, use "" for no parameters and NULL for no type checking |
h | the handler |
user_data | pointer saved and passed to handler |
coerce | is true if you want to allow automatic coercion of types. coerce is ignored if parse is false. |
parse | is true if you want O2 to construct an argv argument vector to pass to the handle |
If the address is only the service name with no trailing slash, the handler will match any message to the service. Pay attention to the parse flag – if true and types do not match, the message will not be delivered.
Addresses should not conflict: An address should not match another address, and for every pair of addresses X and Y, X/ should not be a prefix of Y. Otherwise, the last handler added will remove all conflicting handlers.
A handler for /W/X will not handle children at /W/X/Y and /W/X/Z, and in fact causes messages to /W/X/Y and /W/X/Z to be ignored. The only way to receive multiples addresses with one handler is to handle /W, i.e. to install a handler for just the service.
void o2_msg_data_print | ( | o2_msg_data_ptr | msg | ) |
print a message from msg_data_ptr to stdout
For debugging, this function will print a human-readable representation of a message and its parameters.
msg | a message to be printed |
O2err o2_network_enable | ( | bool | enable | ) |
Disable external nework connections.
Set the default to enable or disable network connections to other hosts. This setting can only be changed before O2 is started with o2_initialize() or between calls to o2_finish() and o2_initialize. When o2_initialize() is called and network connections are enabled, O2 will try to obtain a local (internal) IP address. If found, O2 will then try to obtain a public IP address (assuming Internet connections are enabled, see o2_internet_enable). At the conclusion of this 2-stage initialization, the process will receive an O2 name of the form :internal:port. If a public IP address is not found, the public port is 0.0.0.0, which indicates no Internet connection. If no local IP address is found, or if the only known address is 127.0.0.1 (localhost), then the local IP address becomes 127.0.0.1 with the public IP address 0.0.0.0. If the default for network connections is set to false with o2_network_enable, initialization immediately sets the lcoal IP to 127.0.0.1 and the public IP to 0.0.0.0. In any case, if the local IP is 127.0.0.1, then discovery messages are only sent to localhost, and no discovery messages are broadcast. Even then, O2 can still open connections for OSC. OSC connections depend only on network connectivity and ignore the o2_network_enable setting.
When the network is disabled, O2 processes can still interconnect within the local host. If an ensemble is expected to run only within local host processes, external connections (including possible security threats) can be blocked using this option.
Network access can also be disabled by passing 'N' as a character in the string parameter of o2_debug_flags.
enable | Use true to enable or false to disable networking. |
int o2_parse_name | ( | const char * | name, |
char * | public_ip, | ||
char * | internal_ip, | ||
int * | port | ||
) |
Parse Public:Local:Port string and extract fields.
name | the full PublicIP:LocalIP:PortNumber string |
public_ip | address where public_ip is written, at least O2N_IP_LEN long |
internal_ip | address where internal_ip is written, at least O2N_IP_LEN long |
port | address where the port number is written |
O2err o2_poll | ( | void | ) |
Process current O2 messages.
Since O2 does not create a thread and O2 requires active processing to establish and maintain connections, the O2 programmer (user) should call o2_poll periodically, even if not offering a service. o2_poll runs a discovery protocol to find and connect to other processes, runs a clock synchronization protocol to establish valid time stamps, and handles incoming messages to all services. o2_poll should be called at least 10 times per second. Messages can only be delivered during a call to o2_poll so more frequent calls will generally lower the message latency as well as the accuracy of the clock synchronization (at the cost of greater CPU utilization). Human perception of timing jitter is on the order of 10ms, so polling rates of 200 to 1000 are advised in situations where rhythmic accuracy is expected.
int o2_roundtrip | ( | double * | mean, |
double * | min | ||
) |
Get network round-trip information.
*mean
to the mean round-trip time and *min
to the minimum round-trip time of the last 5 (where 5 is the value of CLOCK_SYNC_HISTORY_LEN) clock sync requests. Otherwise, O2_FAIL is returned and *mean
and *min
are unaltered.Note: You can get this information from a remote process by sending a message to !@public:internal:port/cs/rt
, where @public:internal:port
is the :internal:port string for a process. (One way to get this is to call o2_get_addresses
and construct a:internal:port process name from the information returned. But then you can just call o2_roundtrip
for the local process round trip information. To get remote process names, you can create a handler for /_o2/si
. The process name is provided whenever one of its services is created or otherwise changes status.) The type string for !@public:internal:port/cs/rt
is "s", and the parameter is an O2 address. When the message is received, a reply is sent to the address with the type string "sff", and the parameters are (1) the process:internal:port name, (2) the mean of recent round trip times to the reference clock, and (3) the minimum of recent round trip times. (The clock is set using the minimum, so this number is an upper bound on the clock skew for this process.
int o2_run | ( | int | rate | ) |
Run O2.
Call o2_poll at the rate (in Hz) indicated. Returns if a handler sets o2_stop_flag to non-zero.
O2err o2_schedule_msg | ( | O2sched_ptr | scheduler, |
O2message_ptr | msg | ||
) |
/brief Schedule a message.
Rather than sending a message, messages can be directly scheduled. This is particulary useful if you want to schedule activity before clock synchronization is achieved. For example, you might want to poll every second waiting for clock synchronization. In that case, you need to use the local scheduler (o2_ltsched). o2_send will use the global time scheduler (o2_gtsched), so your only option is to construct a message and call o2_schedule_msg.
scheduler | a pointer to a scheduler (&o2_ltsched or &o2_gtsched ) |
msg | a pointer to the message to schedule |
The message is scheduled for delivery according to its timestamp (which is interpreted as local or global time depending on the scheduler).
The message is delivered immediately if the time is zero or less than the current time; however, to avoid unbounded recursion, messages scheduled within handlers are appended to a "pending messages" queue and delivered after the handler returns.
O2err o2_service_free | ( | const char * | service_name | ) |
Remove a local service.
The #service_name corresponds to the parameter previously passed to o2_service_new or o2_osc_delegate. Note that if an OSC port forwards to this service (see o2_osc_port_new), the port remains open, but the OSC messages will be dropped. See o2_osc_port_free.
service_name | the name of the service |
const char * o2_service_getprop | ( | int | i, |
const char * | attr | ||
) |
get a property value from a saved list of services
i | the index of the service with the properties |
attr | an attribute to search for |
const char * o2_service_name | ( | int | i | ) |
get a service name from a saved list of services
See o2_services_list. Do not free the returned value. Instead, call o2_services_list_free. The pointer will be invalid after calling o2_services_list_free.
i | the index of the service, starting with zero |
O2err o2_service_new | ( | const char * | service_name | ) |
Add a service to the current process.
Once created, services are "advertised" to other processes with matching ensemble names, and messages are delivered accordingly. E.g. to handle messages addressed to "/synth/volume" you call
and define synth_volume_handler
(see the type declaration for O2method_handler and o2_method_new) User-created service names must begin with a letter. Normally, services should be unique across the ensemble. If #service_name is already locally defined in this process (by a previous call to o2_service_new or o2_osc_delegate), this call will fail, returning O2_SERVICE_EXISTS. If matching service names are defined in two different processes, the process with the highest IP and port number (lexicographically) will provide the service. However, due to the distributed and asynchronous nature of O2, there may be some intervening time (typically a fraction of a second) during which a service is handled by two different processes. Furthermore, the switch to a new service provider could redirect a stream of messages, causing unexpected behavior in the ensemble.
service_name | the name of the service |
const char * o2_service_process | ( | int | i | ) |
get a process name from a saved list of services
See o2_services_list. Do not free the returned value. Instead, call o2_services_list_free. The pointer will be invalid after calling o2_services_list_free.
i | the index of the service |
const char * o2_service_properties | ( | int | i | ) |
get the properties string from a saved list of services
See o2_services_list. Properties have the form: "attr1:value1;attr2:value2;...", where attributes are alphanumeric, and values can be any string with colon represented by "\:", semicolon represented by "\;", and slash represented by "\\". Escape characters are not removed, and the result should not be modified or freed. Properties end in ";".
Do not free the returned value. Instead, call o2_services_list_free. The pointer will be invalid after calling o2_services_list_free.
i | the index of the service |
O2err o2_service_property_free | ( | const char * | service, |
const char * | attr | ||
) |
remove an attribute and value property from a service
Search for a service offered by the current process named by #service. Then remove both the attribute and value of the property named by #attribute.
service | the name of a service offered by this process |
attr | the attribute name |
int o2_service_search | ( | int | i, |
const char * | attr, | ||
const char * | value | ||
) |
find a service matching attribute/value pair
i | the index from which to start searching |
attr | the attribute to search |
value | the value substring that must match. To match a prefix, use ":prefix"; to match a suffix, use "suffix;"; to make an exact full match, use ":value;". Since the value itself may contain ':', ';', and '\' characters, these must be escaped with '\'. (Unfortunately, in a C or C++ literal string, the '\' itself must also be escaped, so to search for an exact match to the value "x;y", escape ';' to get the 4 character string denoted in C by "x\\;y", then add ':' and ';' to indicate an exact match: ":x\\;y;") |
O2err o2_service_set_property | ( | const char * | service, |
const char * | attr, | ||
const char * | value | ||
) |
set an attribute and value property for a service
service | the name of a service offered by this process |
attr | the attribute name |
value | the value string; this string will be escaped. Do not include escape characters in #value |
Note that each call will broadcast the property change to every other O2 process in the ensemble. Therefore properties are not recommended for publishing values frequently to clients, expecially if multiple properties are typically updated in sequence, e.g. X, Y, Z coordinates, which would result in 3 messages to each other process. Consider sending X, Y, Z together in a normal O2 message, and consider using taps if the "publisher" does not know all the "subscribers." Also note that properties can be written by multiple service providers, all offering services with the same name, but properties are only readable from the current service provider. If the current service provider changes to a new process, there can be temporary inconsistent views of service properties across the O2 ensemble.
const char * o2_service_tapper | ( | int | i | ) |
get a tapper name from a saved list of services
See o2_services_list. Do not free the returned value. Instead, call o2_services_list_free. The pointer will be invalid after calling o2_services_list_free.
i | the index of the service |
int o2_service_type | ( | int | i | ) |
get a type from a saved list of services
See o2_services_list. The return value indicates the type of the service: O2_LOCAL (4) if the service is local, O2_REMOTE (5) if the service is remote, O2_BRIDGE (6) if the service is a bridge to an o2lite, websocket or shared memory process (or some other bridge protocol), O2_TO_OSC (7) if the service delegates to an OSC server, and O2_TAP (8) for each tapper of the service.
i | the index of the service, starting with zero |
O2err o2_services_list | ( | void | ) |
list known services and taps
Currently active services and taps can be queried by calling o2_services_list. An internal snapshot of services and taps is saved. Information can then be accessed by calling o2_service_name, o2_service_type, o2_service_process, o2_service_tapper, and o2_service_properties. When the information is no longer needed, call o2_services_list_free.
Only active services and their tappers are reported. If there are two services with the same name, only the active one is reported. Taps on active services are reported even if the tapper does not exist.
O2err o2_services_list_free | ( | void | ) |
free the list of known services and taps
Call this function when the information captured by o2_services_list is no longer needed.
Set discovery period.
O2 discovery messages are broadcast periodically in case a new process has joined the ensemble. The default period is 4 seconds. If there are N processes, each host will receive N/4 discovery messages per second. Since there are 16 discovery ports, a discovery message from given process could be received every 64 seconds. (Note, however, that new processes send more frequently, so if messages are not dropped, discovery of new processes will happen much faster than the worst-case.)
You can change the polling period from 4s by calling this function. The new polling period takes effect when the next discovery message is sent at the end of the current polling period.
However, discovery is limited to approximately 10 incoming messages/second based on the number of known processes, so when more remote processes are discovered, the polling period may increase.
period | the requested polling period; a minimum of 0.1s is enforced; 4s is the default (recommended). |
void o2_sleep | ( | int | n | ) |
Suspend for n milliseconds.
n | number of milliseconds to sleep |
int o2_status | ( | const char * | service | ) |
Check the status of the service.
service | the name of the service |
Note that codes are carefully ordered to allow testing for categories:
o2_status(service) > O2_FAIL
, o2_status(service) >= 0
, or o2_status(service) >= O2_LOCAL_NOTIME
.o2_status(service) >= O2_LOCAL
. Note that status can change over time, e.g. the status of a remote service will be O2_UNKNOWN until the service is discovered. It will then change to O2_REMOTE_NOTIME until both the sender and receiver achieve clock synchronization and share their synchronized status, and finally the status will become O2_REMOTE.In the cases with no clock sync, it is safe to send an immediate message with timestamp = 0, but non-zero timestamps are meaningless because either the sending process has no way to obtain a valid timestamp or the receiver has no way to schedule delivery according to a timestamp.
Messages to services are dropped if the service has not been discovered. Timestamped messages (timestamp != 0) are dropped if the sender and receiver are not clock-synchronized. (o2_status(service) >= O2_LOCAL
).
A special case is with BRIDGE
and OSC
services. In these cases, the O2 process offering the service can either schedule the messages locally, sending them according to the timestamp (and suffering some network latency), or if the destination process is synchronized, messages can be forwarded immediately for more precise scheduling at their final destination. O2 does not provide any way for clients/users to determine which of these methods is in effect, and in the case of messages being forwarded by an intermediary O2 process, the originator of the message cannot determine whether the service is offered by an O2 server on the local network, by an OSC server, or through a bridge to another network such as Bluetooth. The status at the originator will be simply O2_REMOTE or O2_REMOTE_NOTIME.
When the status of a service changes, a message is sent with address !_o2/si
. The type string is "sis" and the parameters are (1) the service name, (2) the new status, and (3) the :internal:port string of the process that offers (or offered) the service.
const char * o2_status_to_string | ( | int | status | ) |
retrieve text version of an O2status
status | a status code |
note that the parameter is of type O2status while o2_status returns int. Therefore o2_status_to_string(o2_status("service")) is an invalid conversion from int to O2status. This is because o2_status can also return an O2err (a negative value). You can pass an error value to o2_status_to_string and it will return "O2_FAIL" (rather than a specific error description). Normally, you will retrieve an int from o2_status and call o2_status_to_string((O2status) stat).
O2err o2_tap | ( | const char * | tappee, |
const char * | tapper, | ||
O2tap_send_mode | send_mode | ||
) |
install tap to copy messages from one service to another
tappee | the service to be tapped |
tapper | the existing local service to which copies are sent |
send_mode | Send the tap message using the same method as the original message with TAP_KEEP, by reliable (TCP) method with TAP_RELIABLE, or by best effort (UDP) method with TAP_BEST_EFFORT. |
Have messages delivered to #tappee copied to #tapper. After this call, messages to #tappee are first delivered. Then, if #tappee is local and #tapper exists, the message is copied, modified by replacing the service name with #tapper, and sent. There may be multiple taps on a single service, resulting in the delivery of multiple copies.
Taps can be used to implement a publish/subscribe model. The publisher creates a local service and need not install any message handlers. Subscribers install taps on the service to receive messages. It is more efficient for the tappee to be in the publisher process, but actually any process can publish to the service with the added cost of sending a message to the tappee's process.
Messages with timestamps are held according to their timestamps. Tappers receive the messages after the actual message dispatch. Therefore, publish/subscribe using taps cannot achieve precise timing with timestamps. (The recommended approach to timed publish/subscribe is to use properties: A service subscribing to X sets a property such as "X-subscribe:yes". Publishers for X periodically search for services with the ";X-subscribe:" attribute with value "yes" and (re)build a subscriber list. To publish, the publisher explicitly sends a timed message to each subscriber.)
Bundles are first unbundled before delivery, so tappers will not receive bundles. Note that bundled messages each contain a timestamp and service name which might differ from those of the bundle, so tapping is only applied individually to each bundled message when it is actually delivered.
While services are normally independent of processes (for example, a new service can override an existing one in another process), tappers are tied to processes and cannot be overridden by another service of the same name. It is not redirected to another service provider. Also, unlike ordinary message delivery that delivers one message even if there are multiple processes offering the service, there can be multiple tappers, even with the same service names, and each tapper receives a copy of every message delivered to the tappee.
The lifetime of a tap is independent of the lifetimes of the tappee and the tapper, but the tap is tied to a process, so if the process is terminated, the tap is destroyed throughout the O2 ensemble. For example, if the tappee's process crashes and restarts, and if the tapper belongs to another process that survives, then the tap will be reinstated on the tappee. Similarly, a tap may be created before the tappee or the tapper. Calling o2_service_free on a tapper will not free the tap, so tap messages will continue to be delivered to the tap's process, and if the tappper service is (re)created, these tap messages will be delivered to the new tapper service.
O2time o2_time_get | ( | void | ) |
Get the estimated synchronized global O2 time.
This function returns a valid value either after you call o2_clock_set, making the local clock the reference clock for the O2 ensemble, or after O2 has finished discovering and synchronizing with the reference clock. Until then, -1 is returned.
The clock accuracy depends upon network latency, how often o2_poll is called, and other factors, but
O2err o2_untap | ( | const char * | tappee, |
const char * | tapper | ||
) |
remove tap from service
tappee | the service that is tapped |
tapper | the service to which copies are sent |
Remove a previously installed tap
int o2_version | ( | char * | version | ) |
get O2 version number
If version is not NULL, a version string is written in the form major.minor.patch, where fields are up to 3 digits. Thus, the length of version must be at least 12 bytes (including EOS).
|
extern |
Current scheduler.
When a timed message is delivered by a scheduler, o2_active_sched is set to pount to the scheduler. A handler that constructs and schedules a message can use this pointer to continue using the same scheduler.
|
extern |
TO DO: see each error return has the right error code tests that generate error messages
|
extern |
Scheduler that schedules according to global (reference) clock time.
Scheduling on this scheduler (including sending timed messages) will only work after clock synchronization is obtained. Until then, timed message sends will fail and attempts to o2_schedule_msg will fail.
|
extern |
Scheduler that schedules according to local clock time.
It may be necessary to schedule events before clock synchronization with the reference clock, or you may want to schedule local processing that ignores any changes in clock time or clock speed needed to stay synchronized with the reference clock (even though these should be small). For example, O2 uses the local time scheduler to schedule the clock synchronization protocol, which of course must run before clock synchronization is obtained.
In these cases, you should schedule messages using o2_ltsched.
|
extern |
set this flag to stop o2_run
Some O2 processes will initialize and call o2_run, which is a simple loop that calls o2_poll. To exit the loop, set o2_stop_flag to #true