Loading...
Searching...
No Matches
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_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

o2_blob_ptr | o2_blob_new (uint32_t size)
| Allocate a blob.
int | o2_send_start ()
| Prepare to build a message.
int | o2_add_float (float f)
| add a `float` to the message (see o2_send_start())
int | o2_add_string_or_symbol (o2_type tcode, const char *s)
| This function suppports o2_add_symbol() and o2_add_string() Normally, you should not call this directly.
int | o2_add_blob (o2_blob_ptr b)
| add an `o2_blob` to the message (see o2_send_start()), where the blob is given as a pointer to an o2_blob object.
int | o2_add_blob_data (uint32_t size, void *data)
| add an `o2_blob` to the message (see o2_send_start()), where the blob is specified by a size and a data address.
int | o2_add_int64 (int64_t i)
| add an `int64` to the message (see o2_send_start())
int | o2_add_double_or_time (o2_type tchar, double d)
| This function supports o2_add_double() and o2_add_time() Normally, you should not call this directly.
int | o2_add_int32_or_char (o2_type tcode, int32_t i)
| This function supports o2_add_int32() and o2_add_char() Normally, you should not call this directly.
int | o2_add_midi (uint32_t m)
| add a short midi message to the message (see o2_send_start())
int | o2_add_only_typecode (o2_type 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.
int | o2_add_vector (o2_type element_type, int length, void *data)
| add a vector
int | o2_add_message (o2_message_ptr msg)
| add a message to a bundle
o2_message_ptr | o2_message_finish (o2_time time, const char *address, int tcp_flag)
| finish and return the message.
o2_message_ptr | o2_service_message_finish (o2_time time, const char *service, const char *address, int tcp_flag)
| finish and return a message, prepending service name
void | o2_message_free (o2_message_ptr msg)
| free a message allocated by o2_send_start().
int | o2_send_finish (o2_time time, const char *address, int tcp_flag)
| send a message allocated by o2_send_start().

## 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 o2_argv. (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 o2_arg_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 point to zero bytes and therefore should not be used.

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('B') 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('['). 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(']'). 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 o2_arg_ptr to an integer, an o2_arg_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('v') or o2_get_next('['). 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 o2_arg_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 o2_arg_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. If coercion is provided from the type indicated on the left on some row to the types corresponding to columns where an "x" appears ("*" 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 "</em>": Coercion succeeds from 0 to False and from non-zero to True, otherwise coercion fails.

## Function Documentation

## o2_add_message()

int o2_add_message | ( | o2_message_ptr | msg | ) |

add a message to a bundle

Parameters

  msg | a 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_message_free(msg) after calling o2_add_message(msg).

## o2_add_vector()

int o2_add_vector | ( | o2_type | element_type,
|  | int | length,
|  | void * | data
| ) |  |

add a vector

Parameters

  element_type | the type of the vector elements
  length | the number of vector elements
  data | the 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()

o2_blob_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

  size | The 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()

o2_message_ptr o2_message_finish | ( | o2_time | time,
|  | const char * | address,
|  | int | tcp_flag
| ) |  |

finish and return the message.

Parameters

  time | the timestamp for the message (0 for immediate)
  address | the O2 address pattern for the message
  tcp_flag | boolean if true, send message reliably

Returns
  the address of the completed message, or NULL on error

The message must be freed using o2_message_free() or by calling o2_message_send(). If the message is a bundle (you have added messages using o2_add_message()), do not call o2_message_finish(). Instead, call o2_service_message_finish().

## o2_message_free()

void o2_message_free | ( | o2_message_ptr | msg | ) |

free a message allocated by o2_send_start().

This function is not normally used because O2 functions that send messages take "ownership" of messages and (eventually) free them.

## o2_send_finish()

int o2_send_finish | ( | o2_time | time,
|  | const char * | address,
|  | int | 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

  time | the timestamp for the message
  address | the destination address including the service name. To send a bundle to a service named foo, use the address "#foo"
  tcp_flag | boolean that says to send the message reliably. Normally, true means use TCP, and false means use UDP.

Returns
  O2_SUCCESS if success, O2_FAIL if not.

## o2_send_start()

int o2_send_start | ( |  | ) |

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()

o2_message_ptr o2_service_message_finish | ( | o2_time | time,
|  | const char * | service,
|  | const char * | address,
|  | int | tcp_flag
| ) |  |

finish and return a message, prepending service name

Parameters

  time | the timestamp for the message (0 for immediate)
  service | a string to prepend to address or NULL.
  address | the O2 address pattern for the message. If this is a bundle, address should be "" (empty string)
  tcp_flag | boolean if true, send message reliably

Returns
  the address of the completed message, or NULL on error

The message must be freed using o2_message_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.