NAV
C89 Safe C++ Fast C++ Python QML OFX Unity3D Pd Max Processing SuperCollider
  • Introduction
  • Setup
  • Basic networking
  • Advanced networking
  • Node and parameter attributes
  • Preset support
  • Utilities
  • Introduction

    libossia is a modern C++, cross-environment distributed object model for creative coding.

    It allows to declare the architecture of your creative coding application's functions as a tree of OSC nodes and parameters. These nodes/parameters can have attributes, which allow to declare many of their properties, such as their values, types, units, ranges, etc....

    This OSC tree-based architecture (coined "device" in the OSSIA terminology) can then be exposed over the network under several protocols, some of which allow this architecture, and the properties and values of its nodes, to be fully explored and queried. For now, protocols available in the implemenations are: plain OSC, OSCquery, Midi - more are part of libossia and will be made available in the future.

    libossia offers bindings and implementations for several environments: PureData, Max/MSP, Python, Unity3D, QML, Faust, SuperCollider.

    Here's a quick explanation of the bindings:

    The interactive sequencer score is an example of software based on libossia. An exhaustive (Doxygen) documentation can be found at http://ossia.github.io/libossia/html/index.html

    Setup

    Installation/Compilation

    // device.c:
    #include <ossia-c/ossia-c.h>
    int main(int argc, char** argv) { }
    
    // Assuming the release package is extracted here:
    
    // Linux:
    gcc -std=c89 device.c -Iossia/include -Lossia/lib -lossia
    
    // Mac:
    clang -std=c89 device.c -Iossia/include -Lossia/lib -lossia
    
    // Windows with MSVC: run in a Visual Studio shell:
    cl.exe -std=c89 device.c -Iossia/include ossia/lib/ossia.lib
    

    Basic networking

    Local OSCQuery device

    A device represents a tree of parameters.

    Local devices map to real parameters on the executable libossia is used with. For instance the frequency of a filter, etc.

    Remote devices are mirror images of local devices on other applications: remote controls, mobile apps, etc. Every parameter in a local device will be synchronized with the remote devices that connected to it.

    Devices can be mapped to different protocols: OSC, OSCQuery, Midi, etc. For the sake of simplicity, some bindings tie together device and protocol implementation.

    We use OSCQuery as an example of protocol here. Once a device has been created, it is possible to check what's in it by going to http://localhost:5678.

    For more information on the OSCQuery protocol, please refer to the proposal.

    #include <ossia-c/ossia-c.h>
    ...
    ossia_protocol_t proto = ossia_protocol_oscquery_server_create(1234, 5678);
    ossia_device_t dev = ossia_device_create(proto, "supersoftware");
    ...
    ossia_device_free(dev);
    ossia_protocol_free(proto);
    

    Creating nodes

    The nodes in the device are simply called "nodes" in the API. Nodes are identified with the OSC parameter syntax: /foo/bar.

    Nodes per se don't carry any value; they have to be extended with parameters to be able to send and receive messages.

    When multiple nodes with the same name are created, they will be appended instance numbers, separated with a dot. E.g. when duplicating the node /foo, its duplicates will be named /foo.1, /foo.2, etc… This allows to have a cleaner representation of the address/namespace, as is demonstrated on the figures below:

    ossia_protocol_t proto = ossia_protocol_oscquery_server_create(1234, 5678);
    ossia_device_t dev = ossia_device_create(proto, "supersoftware");
    ossia_node_t root = ossia_device_get_root_node(dev);
    
    ossia_node_t a_node = ossia_node_create(root, "/foo/blu");
    

    When multiple nodes with the same name are created, they will take instance numbers:

    ossia_node_t root = ...;
    ossia_node_create(root, "/foo/blu"); // /foo/blu
    ossia_node_create(root, "/foo/blu"); // /foo/blu.1
    ossia_node_create(root, "/foo/blu"); // /foo/blu.2
    

    Exploring nodes

    The node structure is a tree that can be explored from the device root node as deep as needed going thru each child recursively. It is also possible to search for a particular node or a set of nodes using #pattern-matching features.

    
    

    Creating parameters

    Each node can only have a single parameter. Parameters can have the following types:

    As an optimisation, specific types for 2, 3, and 4 floats are provided; they are referred to as Vec2f, Vec3f, Vec4f through the code.

    Values can be written to a parameter, and fetched from it.

    This example shows how to create a node, a parameter, send a value to the parameter and how to read it back.

    First we create a parameter

    ossia_node_t a_node = ...;
    
    ossia_parameter_t a_parameter = ossia_node_create_parameter(a_node, FLOAT_T);
    
    
    ```javascript
    
    ~param = OSSIA_Parameter(~some_device, 'int_test', Integer);
    ~sigparam = OSSIA_Parameter(~some_device, 'signal_test', Signal);
    

    Then we can send values to this parameter

    ossia_parameter_push_f(a_parameter, 345.);
    

    And read them

    /* Get the current value */
    ossia_value_t val = ossia_parameter_clone_value(a_parameter);
    
    /* Request the value to the server if any */
    ossia_value_t val = ossia_parameter_fetch_value(a_parameter);
    

    Parameter callbacks

    Parameter callbacks will inform you every time a parameter receives a message. On environments that support this, this will enable listening on the remote end. That is, if a remote device has no callbacks, network messages won't be sent upon modification.

    void my_callback(void* n, ossia_value_t v)
    {
      /* 0 if it's not a float */
      float f = ossia_value_to_float(v);
    
      /* safe */
      float g = ossia_value_convert_float(v);
    
      /* ownership of the value is transfered to the callback */
      ossia_value_free(v);
    }
    ...
    
    ossia_parameter_t a_parameter = ...;
    ossia_parameter_push_callback(a_parameter, my_callback, NULL);
    

    Property binding

    This show how, for environments that support it, ossia objects can integrate with existing property environments.

    Device callbacks

    Device callbacks can be used to react to creation or removal of nodes in a given device.

    Remote OSCQuery Device

    This shows how to connect to an existing OSCquery device, and refresh the image that we have of it.

    #include <ossia-c/ossia-c.h>
    ...
    ossia_protocol_t proto = ossia_protocol_oscquery_mirror_create("ws://localhost:5678");
    ossia_device_t dev = ossia_device_create(proto, "my_mirror");
    ossia_device_update_namespace(dev);
    ...
    ossia_device_free(dev);
    ossia_protocol_free(proto);
    

    Advanced networking

    Local Midi device

    Open a Midi Input/Output port to allow local device to receive/emit Midi protocol messages.

    N/A
    

    Local OSC device

    Map local device namespace using OSC protocol.

    To be controled by a remote OSC client we need to setup a local port where client OSC messages will arrive. Sometimes the local device sends OSC messages back to be observed by a client which needs to setup the client ip and port.

    Remote Midi device

    Connect to a Midi Input/Output port to observe/send Midi protocol messages from/to a remote device.

    Remote OSC device

    Map remote device namespace using OSC protocol.

    To control a remote OSC device we need to setup the remote ip and port. Sometimes a remote OSC device also sends OSC messages back which needs to setup a local port.

    To send and receive values to each parameter, we also have to declare the namespace as any ossia device.

    OSCQuery instances

    Being able to create and remove objects in reaction to OSCQuery messages

    Raw messages

    Being able to send messages without the node actually existing in the tree, e.g. like a "basic" OSC library

    Pattern matching

    Address a set of nodes using pattern matching as defined in the OSC 1.1 specifications.

    Valid patterns are for instance:

    Because of the way instances of nodes are created when duplicating them (see #creating-nodes), we have added a special wildcard, '!', that matches all instances including the original one, e.g. /foo! matches /foo, /foo.1 and /foo.bob

    Brace expansion is also implemented, which allows for more matching, such as:

    Then it is possible to send and receive values to/from the set of pattern matching nodes.

    Getting all nodes that match a pattern

    ossia_node_t node = ...;
    ossia_node_t* data;
    size_t sz;
    ossia_node_find_pattern(node, "/foo/bar.*", &data, &sz);
    ...
    ossia_node_array_free(data);
    

    Sending messages to multiple nodes

    // Do a loop
    

    Receiving messages from multiple nodes

    N/A
    

    Batch node creation

    It is posible to create several nodes in a raw using regex-like expressions similar to the ones used when doing pattern matching.

    Only [] and {} are possible. e.g.

    /foo/{bar,baz}.[0-9][0-9]/bob.{3..12..2}

    ossia_node_t node = ...;
    ossia_node_t* data;
    size_t sz;
    ossia_node_create_pattern(node, "/foo/bar.*", &data, &sz);
    ...
    ossia_node_array_free(data);
    

    Node and parameter attributes

    This part presents the attributes that can be set on nodes and parameters.

    When using OSCQuery, all attribute changes will propagate across the network, except mute which is local. The "enabled/disabled" attribute has the same effect but does propagate.

    Access mode

    Access mode is a metadata that categorizes parameters between:

    For instance:

    ossia_parameter_t param = ...;
    ossia_parameter_set_access_mode(param, BI);
    

    Domain (min/max, set of values)

    Domains allow to set a range of accepted values for a given parameter.
    This range can be continuous (between a min and max), or discrete: a set of accepted values.
    A continuous range is only meaningful for nodes with parameters of numerical types (ie ints, floats, vecnfs and some lists) and a discrete range for nodes with parameters of about any types except Impulse

    This sets a node's range between -5 and 5.

    ossia_node_t node = ...;
    ossia_value_t min = ossia_value_create_int(-5);
    ossia_value_t max = ossia_value_create_int(5);
    
    ossia_domain_t dom = ossia_domain_make_min_max(min, max);
    ossia_parameter_set_domain(dom);
    
    ossia_value_free(min);
    ossia_value_free(max);
    ossia_domain_free(dom);
    

    If the domain is an array, it is possible to filter per value, or with a single, shared, min / max.

    Instead of a min / max, it is also possible to give a set of accepted values. Values that don't fit will be rounded to the closest accepted value.

    Bounding mode

    The bounding mode tells what happens when a value is outside of the min / max:

    The default is FREE.

    ossia_parameter_t param = ...;
    ossia_parameter_set_bounding_mode(param, CLIP);
    

    Repetition filter

    When the repetition filter is enabled, if the same value is sent twice, the second time will be filtered out.

    ossia_parameter_t param = ...;
    ossia_parameter_set_repetition_filter(param, 1);
    

    Units

    Units give a semantic meaning to the value of a parameter.

    ossia_parameter_t param = ...;
    ossia_parameter_set_unit(param, "color.rgba");
    

    Units are sorted by categories (coined "dataspace" ): every unit in a category is convertible to the other units in the same category.
    Every category has a neutral unit to/from which conversions are made.

    An unit, when setting it as a parameter's attribute, can be expressed as a string in the form:
    - "category.unit" (such as "position.cart2D"),
    - only with the unit name (such as "cart2D", those being all unique),
    - or with "nicknames", that are indicated in parentheses, after the unit name
    A list of all supported units is given below.

    List of Units

    Position

    Orientation

    Color

    Angle

    Distance

    Time

    Gain

    Speed

    Extended type

    Extended types, just like units, are here to give an indicative meaning to a parameter. They can also be used to enable some optimizations.

    libossia proposes the following types:

    ossia_parameter_t param = ...;
    ossia_parameter_set_extended_type(param, "filepath");
    

    Instance bounds

    For nodes that can have instantiatable children, this sets the minimum and maximum number of children that can exist. This is not enforced and is only to be relied upon as a metadata.

    This is an optional attribute.

    This sets the instance bounds to [0 - 100].

    ossia_node_t node = ...;
    ossia_node_set_instance_bounds(node, 0, 100);
    ossia_node_unset_instance_bounds(node);
    
    int min, max;
    int ok;
    ossia_node_get_instance_bounds(node, &min, &max, &ok);
    if(ok) {
      // min and max are meaningful
    }
    

    Description

    An optional textual description.

    ossia_node_t node = ...;
    ossia_node_set_description(node, "a pretty node");
    ossia_node_unset_description(node);
    
    const char* desc  = ossia_node_get_description(node);
    ossia_string_free(desc);
    

    Tags

    An optional array of tags for nodes, expressed as one string per tag.

    ossia_node_t node = ...;
    const char* tags[2] = {"video", "funny"};
    ossia_node_set_tags(node, tags, 2);
    

    Priority

    Nodes with the highest priority should execute first.

    ossia_node_t node = ...;
    ossia_node_set_priority(node, 10);
    ossia_node_unset_priority(node);
    

    Recall_safe

    Nodes with the recallSafe attribute set to 1 won’t be recalled by presets (or other state recall).

    ossia_node_t node = ...;
    ossia_node_set_recall_safe(node, 10);
    ossia_node_unset_recall_safe(node);
    

    Refresh rate

    An optional value that says how often a value should be updated. Currently does not work in all implementations.

    ossia_node_t node = ...;
    ossia_node_set_refresh_rate(node, 10);
    ossia_node_unset_refresh_rate(node);
    

    Step size

    An optional value that says by which increment a value should change, for instance in a value editor.

    ossia_node_t node = ...;
    ossia_node_set_value_step_size(node, 10);
    ossia_node_unset_value_step_size(node);
    

    Default value

    A default value for a given node. Useful for resetting to a default state.

    ossia_node_t node = ...;
    ossia_value_t val = ossia_value_create_float(23.4);
    
    ossia_node_set_default_value(node, val);
    ossia_value_free(val);
    
    val = ossia_node_get_default_value(node);
    if(val)
      ossia_value_free(val);
    
    ossia_node_set_default_value(node, NULL);
    

    Critical

    This attribute informs the network protocol that the value has a particular importance and should if possible use a protocol not subject to message loss, eg TCP instead of UDP. This is useful for instance for "play" messages.

    ossia_node_t node = ...;
    ossia_node_set_critical(node, 1);
    ossia_node_set_critical(node, 0);
    

    Disabled

    This attribute will disable a node: it will stop receiving/sending messages from/to the network.

    ossia_node_t node = ...;
    ossia_node_set_disabled(node, 1);
    

    Muted

    This attribute will disable a node: it will stop sending messages to the network. Unlike the "disabled" attribute, it won't propagate to other mirrored devices.

    ossia_node_t node = ...;
    ossia_node_set_muted(node, 1);
    

    Hidden

    This attribute is to use for nodes that are not to be exposed to the network.

    ossia_node_t node = ...;
    ossia_node_set_hidden(node, 1);
    

    Zombie

    This is a read-only attribute: it informs of whether a node is in a zombie state. A zombie node is an node in a remote device, whose source has been removed. It is kept in the mirrors but marked as such.

    ossia_node_t node = ...;
    int z = ossia_node_get_zombie(node);
    

    Preset support

    Loading and saving presets

    Ossia provides preset handling. Files can be loaded and save to the disk to set the state of the device tree.

    Create a preset from a device:

    ossia_preset_t preset;
    ossia_devices_make_preset(device, &preset);
    

    Write the preset to a file:

    ossia_presets_write_json(preset, "root_name", "foo/preset.json");
    

    Load the preset:

    ossia_preset_t preset;
    ossia_preset_result res = ossia_presets_read_json(
                                    "path/to/mypreset.json",
                                    &preset);
    if(res != OSSIA_PRESETS_OK) {
      ...
    }
    

    Apply the loaded preset to a device:

    ossia_preset_t preset;
    
    res = ossia_devices_apply_preset(device, preset);
    if(res == OSSIA_PRESETS_OK) {
      // the preset was successfuly applied
    }
    

    (Note that the values for parameters with recall_safe attribute set to 1 will not be recalled)

    Preset instances

    Being able to create new objects in reaction to the loading of a preset

    Utilities

    Logging

    Being able to use the libossia logging facilities