O2 2.0
A communication protocol for interactive music and media applications.
Macros | Functions
Low-Level Message Send

Macros

#define o2_add_symbol(s)   o2_add_string_or_symbol(O2_SYMBOL, s)
 add a symbol to the message (see o2_send_start)
 
#define o2_add_string(s)   o2_add_string_or_symbol(O2_STRING, s)
 add a string to the message (see o2_send_start)
 
#define o2_add_double(d)   o2_add_double_or_time(O2_DOUBLE, d)
 add a double to the message (see o2_send_start)
 
#define o2_add_time(t)   o2_add_double_or_time(O2_TIME, t)
 add a time (double) to the message (see o2_send_start)
 
#define o2_add_int32(i)   o2_add_int32_or_char(O2_INT32, i)
 add an int32 to the message (see o2_send_start)
 
#define o2_add_char(c)   o2_add_int32_or_char(O2_CHAR, c)
 add a char to the message (see o2_send_start)
 
#define o2_add_true()   o2_add_only_typecode(O2_TRUE);
 add "true" to the message (see o2_send_start)
 
#define o2_add_false()   o2_add_only_typecode(O2_FALSE);
 add a "false" to the message (see o2_send_start)
 
#define o2_add_tf(x)   o2_add_only_typecode((x) != 0 ? O2_TRUE : O2_FALSE)
 add a boolean typecode T or F (this differs from o2_add_bool which uses typecode B and adds a 0 or 1 as data)
 
#define o2_add_bool(x)   o2_add_int32_or_char(O2_BOOL, (x) != 0)
 add 0 (false) or 1 (true) to the message (see o2_send_start)
 
#define o2_add_nil()   o2_add_only_typecode(O2_NIL);
 add "nil" to the message (see o2_send_start)
 
#define o2_add_infinitum()   o2_add_only_typecode(O2_INFINITUM);
 add "infinitum" to the message (see o2_send_start)
 
#define o2_add_start_array()   o2_add_only_typecode(O2_ARRAY_START);
 start adding an array
 
#define o2_add_end_array()   o2_add_only_typecode(O2_ARRAY_END);
 finish adding an array
 

Functions

O2blob_ptr o2_blob_new (uint32_t size)
 Allocate a blob. More...
 
O2err o2_send_start (void)
 Prepare to build a message. More...
 
O2err o2_add_float (float f)
 add a float to the message (see o2_send_start)
 
O2err o2_add_string_or_symbol (O2type tcode, const char *s)
 This function suppports o2_add_symbol and o2_add_string Normally, you should not call this directly.
 
O2err o2_add_blob (O2blob_ptr b)
 add an O2blob to the message (see o2_send_start), where the blob is given as a pointer to an O2blob object.
 
O2err o2_add_blob_data (uint32_t size, void *data)
 add an O2blob to the message (see o2_send_start), where the blob is specified by a size and a data address.
 
O2err o2_add_int64 (int64_t i)
 add an int64 to the message (see o2_send_start)
 
O2err o2_add_double_or_time (O2type tchar, double d)
 This function supports o2_add_double and o2_add_time Normally, you should not call this directly.
 
O2err o2_add_int32_or_char (O2type tcode, int32_t i)
 This function supports o2_add_int32 and o2_add_char Normally, you should not call this directly.
 
O2err o2_add_midi (uint32_t m)
 add a short midi message to the message (see o2_send_start)
 
O2err o2_add_only_typecode (O2type typecode)
 This function supports o2_add_true, o2_add_false, o2_add_bool, o2_add_nil, o2_add_infinitum, and others. Normally, you should not call this directly.
 
O2err o2_add_vector (O2type element_type, int length, void *data)
 add a vector More...
 
O2err o2_add_message (O2message_ptr msg)
 add a message to a bundle More...
 
O2message_ptr o2_message_finish (O2time time, const char *address, bool tcp_flag)
 finish and return the message. More...
 
O2message_ptr o2_service_message_finish (O2time time, const char *service, const char *address, bool tcp_flag)
 finish and return a message, prepending service name More...
 
O2err o2_send_finish (O2time time, const char *address, bool tcp_flag)
 send a message allocated by o2_send_start. More...
 

Detailed Description

Rather than passing all parameters in one call or letting O2 extract parameters from a message before calling its handler, these functions allow building messages one parameter at a time and extracting message parameters one at a time. The functions operate on "hidden" messages, so these functions are not reentrant.

To build a message, begin by calling o2_send_start to allocate a message. Then call one of the o2_add_*() functions to add each parameter. Finally, call either o2_send_finish to send the message. You should not explicitly allocate or deallocate a message using this procedure.

To extract parameters from a message, begin by calling o2_extract_start to prepare to get parameters from the message. Then call o2_get_next to get each parameter. If the result is non-null, a parameter of the requested type was obtained and you can read the parameter from the result. Results other than strings, MIDI, and blobs may only remain valid until the next call to o2_get_next, so you should use or copy the value before reading the next one. Values that are not coerced (requiring a copy) are left in the O2 message and have the same lifetime as the message. You should not reuse this storage because the message may have multiple destinations; thus, the message content should not be altered.

A by-product of o2_extract_start and o2_get_next is an argument vector (argv) that can be accessed from O2argv. (This is the same argument vector created automatically when a handler is added with o2_method_new when the parse parameter is true.) A possible advantage of using a sequence of o2_get_next calls rather than simply setting the parse flag is that you can receive messages with various types and numbers of parameters. Also, you can check vector lengths and stop parsing if unacceptable lengths are encountered.

o2_get_next will perform type conversion if possible when the requested type does not match the actual type. You can determine the original type by reading the type string in the message. The number of parameters is determined by the length of the type string, with some exceptions.

Vectors can be coerced into arrays, in which case each element will be coerced as requested. Arrays can be coerced into vectors if each element of the array can be coerced into the expected vector element type. Vector lengths are provided by the message; there is no way to coerce or limit vector lengths or check that the length matches an expected value. (You can determine the length from the return value and of course you can decide to reject the message if the length is not acceptable.)

When a vector is returned, the argument vector has a single element that points to a vector descriptor (the "v" field), which contains the vector element types and the length of the vector (>= 0).

When an array is returned, the argument vector contains the value o2_got_start_array followed by an O2arg_ptr for each element of the array, followed by o2_got_end_array.

When types T (True), F (False), I (Infinitum), or N (Nil) are in the message, there is an entry in the argument vector; however, there is no data associated with these types (other than the type itself), so the pointers should not be used except to test for non-NULL.

In all other cases, the argument vector contains data corresponding to the data item in the message. This may be a pointer into the actual message or a pointer to a temporary location in case the element was coerced to a different type.

When the actual type code in the message is in "TFIN" you should call o2_get_next even though there is no corresponding data stored in the message. The return value, if successful, is a non-NULL pointer that points within or just after the message, but you must not dereference this pointer. (NULL indicates failure as with other type codes. One rationale for calling o2_get_next even when there is nothing to "get" is that you can call o2_get_next(O2_BOOL) to retrieve 'T', 'F', or 'B' types as an int32_t which is 0 or 1. The 'I' and 'N' types are never coerced.

Normally, you should not free the message because normally you are accessing the message in a handler and the message will be freed by the O2 message dispatch code that called the handler.

Arrays denoted by [...] in the type string are handled in a somewhat special way:

If an array is expected, call o2_get_next(O2_ARRAY_START). The return value will be o2_got_start_array on success, or NULL if there is no array. The actual value in the message may be an array or a vector. If it is a vector, the elements of the vector will be coerced to the types requested in successive calls to o2_get_next. After retrieving array elements, call o2_get_next(O2_ARRAY_END). The return value should be o2_got_end_array. NULL is returned if there is an error. For example, suppose you call o2_get_next with characters from the type string "[id]" and the actual parameter is a vector integers ("vi") of length 2. The return values from o2_get_next will be o2_got_start_array, an O2arg_ptr to an integer, an O2arg_ptr to a double (coerced from the integer vector), and finally o2_got_end_array. If the vector length is 1, the third return value will be NULL. If the vector length is 3 (or more), the fourth return value will be NULL rather than o2_got_end_array.

The special values o2_got_start_array and o2_got_end_array are not valid structures. In other words, fields such as o2_got_start_array->i32 are never valid or meaningful. Instead, o2_got_start_array and o2_got_end_array are just 'tokens' used to indicate success in type checking. These values are distinct from NULL, which indicates a type incompatibility.

Note also that vector elements cannot be retrieved directly without calling o2_get_next(O2_VECTOR) or o2_get_next(O2_ARRAY_START). For example, if the actual argument is a two-element integer vector ("vi"), a call to o2_get_next(O2_INT32) will fail unless it is preceded by o2_get_next(O2_VECTOR) or o2_get_next(O2_ARRAY_START).

If a vector is expected, call o2_get_next(O2_VECTOR). The return value will be a non-null O2arg_ptr if the next argument in the actual message is a vector or array, and otherwise NULL. You should not dereference this return value yet...

You must then call o2_get_next with the desired type for vector elements. The return value will be an O2arg_ptr (which will be the same value previously returned) containing v.typ set to the desired type, v.len still set to the number of elements, and v.vi, v.vh, v.vd, v.vf, or v.vc pointing to the possibly coerced elements.

Note that the sequence of calling o2_get_next twice for vectors corresponds to the two type characters used to encode them, e.g. "vi" indicates a vector of integers.

Coercion is supported as follows. An "x" means that coercion is provided from the type indicated on some row to the type corresponding to the column ("\*" indicates special consideration described below.)

*      i h f d t s S T F B b m c N I
*    i x x x x x     * * x            32-bit int
*    h x x x x x     * * x            64-bit int
*    f x x x x x     * * x            float
*    d x x x x x     * * x            double
*    t x x x x x                      time
*    s           x x                  String
*    S           x x                  Symbol
*    T x x x x       x   x            True
*    F x x x x         x x            False
*    B x x x x       * * x            Boolean
*    b                     x          blob
*    m                       x        MIDI
*    c                         x      character
*    N                           x    Nil
*    I                             x  Infinitum
* 

*Entries marked with "*": Coercion is from 0 to False and from non-zero to True.

Coercion fails in all cases not marked.

Function Documentation

◆ o2_add_message()

O2err o2_add_message ( O2message_ptr  msg)

add a message to a bundle

Parameters
msga message or bundle to add
Returns
O2_SUCCESS

This function can be called after o2_send_start. If you add a message to a bundle with this function, you must not call any other o2_add_*() functions. E.g. do not call both o2_add_int32 and o2_add_message on the same message.

This function does NOT free msg. Probably you should call O2_FREE(msg) after calling o2_add_message(msg).

◆ o2_add_vector()

O2err o2_add_vector ( O2type  element_type,
int  length,
void *  data 
)

add a vector

Parameters
element_typethe type of the vector elements
lengththe number of vector elements
datathe vector elements; arranged sequentially in memory in a format determined by element_type. The element type is restricted to a character in "ifhtdc"

◆ o2_blob_new()

O2blob_ptr o2_blob_new ( uint32_t  size)

Allocate a blob.

Allocate a blob and initialize the size field. If the return address is not NULL, copy data (up to length size) to blob->data. You can change blob->size, but of course you should not set blob->size greater than the size parameter originally passed to o2_blob_new.

Caller is responsible for freeing the returned blob using #O2_FREE.

A constructed blob can be added to a message. If you add parameters to a message one-at-a-time, you can use o2_add_blob_data to copy data directly to a message without first allocating a blob and copying data into it.

Parameters
sizeThe size of the data to be added to the blob
Returns
the address of the new blob or NULL if memory cannot be allocated.

◆ o2_message_finish()

O2message_ptr o2_message_finish ( O2time  time,
const char *  address,
bool  tcp_flag 
)

finish and return the message.

Parameters
timethe timestamp for the message (0 for immediate)
addressthe O2 address pattern for the message
tcp_flagboolean if true, send message reliably
Returns
the address of the completed message, or NULL on error

The message must be freed using #O2_FREE or by calling o2_message_send. If the message is a bundle (you have added messages using o2_add_message), the address should be '#' followed by the service name, e.g. "#service1".

◆ o2_send_finish()

O2err o2_send_finish ( O2time  time,
const char *  address,
bool  tcp_flag 
)

send a message allocated by o2_send_start.

This is similar to calling o2_send, except you use a three-step process of (1) allocate the message with o2_send_start, (2) add parameters to it using o2_add_ functions, and (3) call o2_send_finish to send it.

Parameters
timethe timestamp for the message
addressthe destination address including the service name. To send a bundle to a service named foo, use the address "#foo".
tcp_flagboolean that says to send the message reliably. Normally, true means use TCP, and false means use UDP.
Returns
O2_SUCCESS if success.

Typically, o2_send_finish returns O2_SUCCESS. Messages can be queued for later delivery, forwarded for remote delivery, or delivered only to a tapper (see o2_tap), so success and failure are not always easy to distinguish. One (probably) clear case is that if a message is sent to a non-existent service, O2_NO_SERVICE is returned. O2_NO_CLOCK is returned if there is no established clock and the message has a non-zero timestamp. For other situations, see o2_message_warnings.

◆ o2_send_start()

O2err o2_send_start ( void  )

Prepare to build a message.

Returns
O2_SUCCESS if success, O2_FAIL if not.

Allocates a "hidden" message in preparation for adding parameters. After calling this, you should call o2_add_ functions such as o2_add_int32 to add parameters. Then call o2_send_finish to send the message.

◆ o2_service_message_finish()

O2message_ptr o2_service_message_finish ( O2time  time,
const char *  service,
const char *  address,
bool  tcp_flag 
)

finish and return a message, prepending service name

Parameters
timethe timestamp for the message (0 for immediate)
servicea string to prepend to address or NULL.
addressthe O2 address pattern for the message.
tcp_flagboolean if true, send message reliably
Returns
the address of the completed message, or NULL on error

The message must be freed using #O2_FREE or by calling o2_message_send. This function is intended to be used to forward OSC messages to a service, but it is the implementation of o2_message_finish, which simply passes NULL for service.