Sygaldry
|
Copyright 2023 Travis J. West, https://traviswest.ca, Input Devices and Music Interaction Laboratory (IDMIL), Centre for Interdisciplinary Research in Music Media and Technology (CIRMMT), McGill University, Montréal, Canada, and Univ. Lille, Inria, CNRS, Centrale Lille, UMR 9189 CRIStAL, F-59000 Lille, France
SPDX-License-Identifier: MIT
An OSC message consists of its address pattern (called its path in some implementations), a type tag string, and zero or more arguments. This document concerns the compile-time generation of OSC paths and type tag strings by reflecting over an endpoint and its position in a component tree.
An OSC address pattern, here termed an OSC path, is a typically URL-like represetation of an OSC endpoint. In the original OSC specification the only formal requirement for an OSC address pattern is that it should be an OSC string, meaning a 32-bit padded null-terminated ASCII string, that begins with the character /
:
"An OSC Address Pattern is an OSC-string beginning with the character `/` (forward slash)."
The semantics of OSC further stipulates that an OSC server should be structured as an OSC address space, i.e. a tree of OSC container nodes and OSC method leaves, where each node and method is identified by a name that doesn't contain any of the character #*,/?[]{}
and ‘’ '(space). An OSC address is then the fully qualified
/seperated path in the address space that identifies an OSC method, beginning with a
/` character that represents the root of the tree, proceeding through the name of each node leading to the method seperated by slashes, and ending with the name of the method. "The syntax of OSC Addresses
was chosen to match the syntax of URLs."
Notably, this semantics implies that OSC servers should be the authority on the interpretation of the OSC messages that they receive, and that clients of a given server are expected to send messages known to be compatible with the server in order to invoke the execution of the server's a-priori-understood methods. In common practice, this is often not the case. Instead, particularly when making input devices such as a gestural controller, it is just as common for an OSC "client" to send messages to a "server" that reflect the state of the "client's" endpoints, such as the state of a button or slider. In such cases, the client has authority on the interpretation of the messages, which formally don't adhere to the OSC semantics described in the specification.
In either case, whether receiving messages from an authoritative client or sending them to an authoritative server, it is one of the central limitations of OSC as a protocol that no provisions are available to exchange information about the interpretation of an OSC message between participants in the OSC network. Later protocols such as libmapper and OSC-query were developed, among other reasons, to address this shortcoming. A similar deficiency in the ubiquitous MIDI 1.0 protocol is meant to be addressed by some of the recent MIDI 2.0 protocol updates.
For now, we presume that component and endpoint names will naturally tend not to include any of the prohibited characters, except spaces. We will use the `snake_case` respeller to remove spaces, and we will ignore the issue of other prohibited characters for now. Future work should endeavour to verify their absence, or escape prohibted characters e.g. using the 'XX' encoding commonly employed in URLs, where 'XX' are hexadecimal digits for the encoding of the escaped character.
We will leverage the `path_t` metafunction to derive a type list representing the path to an endpoint in the component tree and use this representation to derive the path string.
Generating the OSC path string from the path type list should be a reasonably simple matter of copying the snake_case
spelt name of each node in this path to a static buffer. The implementation is fairly similar to that of the respeller class. We allocate a std::array
with the appropriate size and copy the snake case names of each node to the buffer.
It remains as future work to 32-bit align the end of the path buffer by padding with zeros, as required in the OSC spec. There is also currently no verification that the final path string is free from errors, e.g. that it contains no special characters.
After the address pattern, an OSC message contains a type tag string. This is a 32-bit-aligned trailing-zero-padded null-terminated ASCII string beginning with a comma ,
and containing a sequence of ASCII character type tags that represent the type of the OSC arguments, in order. We wish to provide type-tag string representations for all our supported endpoint helpers.
Notably, unlike the OSC path which depends on an endpoint and its place in the node tree, an OSC type tag string depends only on the endpoint.
Currently, we conservatively stick to OSC 1.0 type tags, avoiding 1.1 extended types, for simplicity and in hopes of improving compatibility.
Following the same strategy as above, we statically allocate a std::array
of appropriate size and constexpr
initialize it with an appropriate string. The size and type of the endpoint is easily determined using the endpoint concepts. In the case of array-like values, the length of the type tag string is the length of the array plus one, padded to be 32-bit aligned. Otherwise, for single-valued endpoints the type tag string is always 4 bytes; one type tag, a comma, and two padding zeros.
In the case of a Bang
endpoint, the type tag string is empty. Otherwise, we access the type of the endpoint value to determine the tag character, and then set the type tag string depending on whether the endpoint is an array or not.
See the OSC pattern matching document.