[LV2] Mapping of OSC to LV2 Atoms

Hanspeter Portner ventosus at airpost.net
Fri Oct 16 08:38:38 PDT 2015

On 15.10.2015 20:29, David Robillard wrote:
> On Fri, 2015-06-19 at 22:33 +0200, Hanspeter Portner wrote:
>> Potential Issues
>> ================
>> * The OSC bundle timestamp and timestamp type 't' must be cast
>>   from unsigned to signed as there are no usigned atoms.
> Unfortunate, but maybe an atom type for specifically this sort of
> timestamp wouldn't be a bad idea.

Are you proposing general purpose unsigned integer Atom types:

typedef struct {
	LV2_Atom atom;  /**< Atom header. */
	uint32_t  body; /**< unsigned Integer value. */
} LV2_Atom_uInt;
typedef struct {
	LV2_Atom atom; /**< Atom header. */
	uint64_t body; /**< unsigned Integer value. */
} LV2_Atom_uLong;

Or a specific Atom type just for OSC timestamps:

typedef struct {
	LV2_Atom atom; /**< Atom header. */
	uint64_t body; /**< unsigned Integer value. */
} LV2_OSC_Timestamp;

OSC timestamps are modified NTP timestamps (offset by ~70years). They
represent a 32.32 fixed point number. If you want to work with the
timestamps, you need to tear them apart no matter what. If we create a
new atom type, we could as well represent intergral and fractional parts

typedef struct {
	uint32_t integral; /**< Integral part. */
	uint32_t fractional; /**< Fractional part. */
} LV2_OSC_Timestamp_Body;

typedef struct {
	LV2_Atom atom; /**< Atom header. */
	LV2_OSC_Timestamp_Body body; /**< Timestamp Body. */
} LV2_OSC_Timestamp;

> Though the time stamps don't seem
> particularly useful in this context, to me.

The main purpose is to preserve all available information. I would find
it useful to be able to set timestamps inside a plugin if the OSC bundle
should be destined to be ejected to the network.

>> I actually only care for the base and extended types but
>> it may make sense to include the exotic types too for
>> the sake of completeness.
>> * The four exotic OSC types 'T'rue, 'F'alse, 'N'il, 'I'nfinitum have
>>   no associated data in OSC. Should they be mapped to atoms and
>>   packed into the arguments tuple or only show up in the message
>>   format string like in OSC?
> I don't know about nil and infinitum, but it seems true and false
> clearly map to Bool.  There is no type string for atom objects, so a
> true bidirectional mapping shouldn't be missing bits and rely on some
> external funny thing (a format string) to fill in the gaps, IMO.

Whether the OSC format string is needed or not depends on whether we can
come up with a unique association for all OSC types (or any we want to
support) with a corresponding LV2 type.

For the basic types we could do without format string.
'i' -> LV2_Atom_Int
'f' -> LV2_Atom_Float
's' -> LV2_Atom_String
'b' -> LV2_Atom_Chunk

For the extended types, we may or may not need a format string,
depending on whether we get a special timestamp/unsigned type or not.
'h' -> LV2_Atom_Long
'd' -> LV2_Atom_Double
't' -> LV2_Atom_Long || LV2_Atom_uLong || LV2_OSC_Timestamp

If 'h' and 't' both map to LV2_Atom_Long, we need the format string for
bidirectional mapping.

The booleans and MIDI we can map without format string.

'T' -> LV2_Atom_Bool {.body = 1}
'F' -> LV2_Atom_Bool {.body = 0}
'm' -> LV2_Atom {.type = MIDI__MidiEvent}

'N' and 'I' are not of great use, imho. Without format string we would
have to give them individual types, too, like for the timestamp? We
could also not implement them...

'N' -> LV2_Atom {.size = 0, .type = 0} || ???
'I' -> ???

There are similar issues with 'c'har and 'S'ymbol, which may not be
discriminatable from 'i' and 's', depending on what we map them to. We
could also not implement them...
'c' -> LV2_Atom_Int || LV2_Atom_Char || ???
'S' -> LV2_Atom_String || LV2_Atom_URID

I've never seen OSC types 'N', 'I', 'c', 'S' being used, we could just
not implement them...

So: If we want to get rid of the format string, we either have to add
the missing unique Atom types or reduce the set of supported OSC types.

I could well do with this reduced set of types: 'ifsbhdtTFm'.

Which approach should we take, with or without format string?

>> * Even more exotic types 'c'har, 'S'ymbol and 'm'idi are seldom
>>   used. 'm' type is special as it's four bytes only and first
>>   byte represents a port number. So it's not fully up to the
>>   definition of MIDI__MidiEvent.
> Our URID mechanism is really a symbol mechanism that should have been
> named as such...

Ah, an OSC symbol ('S') could be mapped to an LV2_Atom_URID, which would
have the positive side-effect to make it discriminatable from a simple
OSC string ('s') being an LV2_Atom_String.

>> * It seems to be possible to send arrayed values with '['
>>   ']', but I've never seen it used anywhere and can't figure
>>   out how it's supposed to work. I guess it could be mapped
>>   to a LV2_Atom_Vector, though.
> A vector must contain all the same type, so this won't work.  You'd have
> to use a tuple.

Right. I'll leave it unimplemented for now, as I have never seen OSC
arrays being used in the first place.

> The main thing lacking on the code side of things from my perspective
> seems to be an actual native/standard representation of OSC
> bundles/messages?  It would be nice to take a buffer containing an OSC
> message and simply call a utility function to write that in atom form to
> a forge (or transmit / work with actual OSC between plugins and
> whatnot).

Sure, this is possible. I have code lying around that does exactly that,
will need to redesign it a bit to match the LV2 API, though, will report
back here when the missing bits have been added.

> Would you be interested in relicensing this under the LV2 license and
> having an official extension based on it?

Turning it into an official extension would be great, as others seem to
be interested, too.

Relicensing more permissively is no problem and makes only sense.

More information about the Devel mailing list