osc.h
Open Sound Control.
#include <sonore/osc.h>- 7types
- 6other declarations
- Guides: MIDI and MPE
On this page
Design notes
The protocol control surfaces speak: TouchOSC, Lemur, Open Stage Control, lighting desks, anything that wants to move a parameter from another machine without MIDI's 7-bit ceiling.
OSC 1.0, the part that is actually used: messages with int32, float32, string and blob arguments, and bundles. Everything is 4-byte aligned and BIG-endian, which is the single most common source of "it works on my machine" in a hand-rolled implementation, so the alignment and byte order are asserted in the tests rather than assumed.
The codec has no networking in it at all: encode()/decode() move bytes, and the UDP socket below is a separate, optional convenience. That split is what lets the codec be tested exhaustively without a network.
Types 7
The 'r' argument: a 32-bit colour, red in the high byte and alpha in the low, big-endian on the wire like every other OSC word.
Its own type rather than an int an application has to take apart, because the byte order is exactly the thing that gets taken apart wrongly: ARGB habits from every graphics API put alpha on top, and a lighting desk sent a colour read that way shows a different one.
One OSC argument: its type tag and its value.
Flat rather than a union: every value field is there, and type says which one is meant -- intValue, floatValue, stringValue and blobValue for the four core types, and the comment on each other Type names its field. The make functions set both, and decodeMessage() fills both, so read the field the type names and no other: an Int32 leaves floatValue at 0, it does not convert. A plain value that allocates for strings and blobs, so building one is not for the audio thread.
One OSC message: an address starting with '/' and its arguments in order. What encode() writes into a datagram and decodeMessage() and decodePacket() read out of one -- a bundle arrives as the messages inside it. encode() refuses an address without the leading '/' by returning no bytes.
An OSC address pattern, parsed and checked once so that matching it against many addresses -- every listener a receiver has -- does not re-parse it for each.
Hands each message to the listeners its address pattern reaches.
Exact matching is still the FAST path: a message whose address has no wildcard in it -- nearly all of them -- is one map lookup, exactly as before patterns existed. Only a pattern pays for a scan, and it is parsed once for the whole scan rather than once per listener.
On whatever thread calls dispatch(), which for a Receiver is the thread that polls it: the UI timer, never the audio thread -- a handler is a std::function and may do anything.
A non-blocking UDP receiver. poll() returns whatever arrived since the last call and never waits, so it is safe to call from a UI timer.
Fire-and-forget UDP sender.
Functions 5
- fn
inline std::vector<uint8_t> encode(const Message& message)Encode one message. Returns the bytes to put in a datagram.osc::encode · osc.h:303 - fn
inline bool decodeMessage(const uint8_t* data, size_t size, Message* out)Decode one message. Returns false on anything malformed rather than guessing: a datagram from the network is untrusted input.osc::decodeMessage · osc.h:362 - fn
inline bool decodePacket(const uint8_t* data, size_t size, std::vector<Message>* out)Decode a datagram that may be a bundle, flattening it to the messages inside. Bundles nest, up to detail::kMaxBundleDepth deep; a deeper packet is refused like any other malformed one.osc::decodePacket · osc.h:492 - fn
inline bool isValidAddress(const std::string& address)A plain OSC address: '/', then parts with none of the pattern or reserved characters in them. What a listener registers at.osc::isValidAddress · osc.h:662 - fn
inline bool matchesPattern(const std::string& pattern, const std::string& address)Doesosc::matchesPattern · osc.h:758patternreachaddress? One call, for a caller with one of each.