- MicroOsc is a simple and lightweight Open Sound Control (OSC) library for the Arduino frameworks that supports Arduino platforms.
- MicroOsc provides a unified API to work over Serial (SLIP) and over UDP (Ethernet or Wifi).
- MicroOsc does not hold on to state. Error checking is minimal.
- MicroOsc was inspired by TinyOSC by Martin Roth.
- Source code located at: https://github.com/thomasfredericks/MicroOsc
MicroOsc currently supports:
-
Full address and format matching
-
Message parsing
-
Message writing
-
Bundle parsing (as individual messages)
-
Send Types
b: blob (byte array)f: floatd: doublei: int (int32)h: int64s: stringm: midiI: impulse (message with no arguments; OSC v1.1)T: TRUE (message with no arguments)F: FALSE (message with no arguments)N: NULL (message with no arguments)
-
Receive Types
b: blob (byte array)f: floatd: doublei: int (int32)s: stringm: midi
MicroOsc will eventually support:
- bundle writing (receiving is supported)
Send types not yet supported:
t: timetag
Receive Types not yet supported:
h: int64t: timetagT: trueF: falseI: impulseN: nil
MicroOsc will probably never support:
- Timetags
- Regular expression matching
There are currently 2 supported transport protocols that are identical except for their initialization:
- Serial (with SLIP)
- UDP (Ethernet or WiFi).
#include <MicroOscSlip.h>
MicroOscSlip<128> myOsc(&Serial); // <#> : # of bytes reserved for incomming messages.In setup() don't forget to start Serial:
Serial.begin(115200);Initialize UDP and network details first.
For WiFi:
#include <WiFiUdp.h>
WiFiUDP myUdp;For Ethernet:
#include <Ethernet.h>
// An EthernetUDP instance to let us send and receive packets over UDP
EthernetUDP myUdp;Include MicroOsc and initiliaze yout instance of MicroOsc.
You can initiliaze MicroOsc with the destination:
#include <MicroOscUdp.h>
unsigned int myReceivePort = 8888;
IPAddress myDestinationIp(192, 168, 1, 210);
MicroOscUdp<1024> myOsc(&myUdp, myDestinationIp, myDestinationPort); // <#> : # of bytes reserved for incomming messages.Or, if you need to set it alter, you can also initiliaze MicroOsc without the destination:
#include <MicroOscUdp.h>
MicroOscUdp<1024> myOsc(&myUdp); // <#> : # of bytes reserved for incomming messages.In setup() don't forget to start your UDP instance:
unsigned int myDestinationPort = 7777;
myUdp.begin(myReceivePort);myReceivePort: port number (unsigned int) that this device (the microcontroller) should listen to.
The destination can be changed during runtime:
myOsc.setDestination( myDestinationIp, myDestinationPort) myDestinationIp: IP address (IPAddress) of the device you want to send messages to.myDestinationPort: port number (unsigned int) of the device you want to send messages to.
To receive OSC messages you must first create a function in which you will check the message address and get the message arguments:
// FUNCTION THAT WILL BE CALLED WHEN AN OSC MESSAGE IS RECEIVED:
void myOscMessageParser( MicroOscMessage& receivedOscMessage) {
// DO MESSAGE ADDRESS CHECKING AND ARGUMENT GETTING HERE
}In loop() you need to trigger the reception of the messages:
myOsc.onOscMessageReceived( myOscMessageParser );MicroOsc will return a reference to a MicroOscMessage when it receives an OSC message. The following functions are members of MicroOscMessage.
/**
* Returns true if the address matches exactly
*/
bool checkOscAddress(const char* address);Example with a MicroOscMessage named receivedOscMessage:
if ( receivedOscMessage.checkOscAddress("/pot") ) {
// ...
}/**
* Returns true if the address and argument type tags match exactly.
*/
bool checkOscAddressAndTypeTags(const char* address, const char * typetags);Example with a MicroOscMessage named receivedOscMessage:
if ( receivedOscMessage.checkOscAddressAndTypeTags("/pot", "i") ) {
// ...
}MicroOsc will return a reference to a MicroOscMessage when it receives an OSC message.
The following functions are members of MicroOscMessage.
/**
* Returns the next argument as a 32-bit int.
* Does not check buffer bounds.
*/
int32_t nextAsInt();Example with a MicroOscMessage named receivedOscMessage:
int32_t intArgument = receivedOscMessage.nextAsInt();/**
* Returns the next argument as a 32-bit float.
* Does not check buffer bounds.
*/
float nextAsFloat();Example with a MicroOscMessage named receivedOscMessage:
float floatArgument = receivedOscMessage.nextAsFloat();/**
* Treats the next argument as a string and returns a pointer to the data as a C string,
* or NULL if the buffer length is exceeded.
*/
const char* nextAsString();WARNING: Do not store the pointer returned by this function. Only use it as read only inside of the function called by onOscMessageReceived().
Example with a MicroOscMessage named receivedOscMessage:
const char * s = receivedOscMessage.nextAsString();/**
* Treats the next argument as a blob of data and sets a pointer with the address to a byte array.
* The pointer is NULL if there was an error.
* Returns the length of the byte blob. Returns 0 if there was an error.
*/
uint32_t nextAsBlob(const uint8_t **blobData);WARNING: Do not store the pointer returned by this function. Only use it as read only inside of the function called by onOscMessageReceived().
Example with a MicroOscMessage named receivedOscMessage:
const uint8_t* blob;
uint32_t length = receivedOscMessage.nextAsBlob(&blob);/**
* Treats the next value as MIDI and sets a pointer with the address to the MIDI data.
* The pointer is NULL if the OSC bounds are exceeded.
* MIDI data always has a length of 4. Bytes from MSB to LSB are: port id, status byte, data1, data2
*/
int nextAsMidi(const uint8_t **midiData);WARNING: Do not store the pointer returned by this function. Only use it as read only inside of the function called by onOscMessageReceived().
Example with a MicroOscMessage named receivedOscMessage:
const uint8_t* midi;
receivedOscMessage.nextAsMidi(&midi);You can also receive lists of arguments with MicroOsc. Everytime you get the value of an argument, an internal pointer moves to the next argument in the list automatically.
For example if you to receive and parse the OSC message /controller ["FREQ", 0.125, "kHz"] you can do the following:
// OSC MSG "/controller" WITH THE ARGUMENT LIST "sfs" (string, float, string)
if ( receivedOscMessage.checkOscAddress("/controller", "sfs") ) {
// GET THE FIRST AS A STRING
const char * firstAsString = receivedOscMessage.nextAsString();
// GET THE SECOND AS A FLOAT
float secondAsFloat = receivedOscMessage.nextAsFloat();
// GET THE THIRD AS A STRING
const char * thirdAsAString = receivedOscMessage.nextAsString();
}MicroOsc provides individual functions for sending a single argument tp all supported types. It also provides an advanced function for sending messages with multiple arguments of the same or mixed types.
Use one of these to send a single argument:
void sendMessage(const char *address, const char *format, ...);
void sendInt(const char *address, int32_t i);
void sendFloat(const char *address, float f);
void sendString(const char *address, const char *str);
void sendBlob(const char *address, unsigned char *b, int32_t length);
void sendDouble(const char *address,double d);
void sendMidi(const char *address,unsigned char *midi);
void sendInt64(const char *address, uint64_t h);Example of sending an int:
int reading = analogRead(1);
myOsc.sendInt("/photo", reading);MicroOsc can sends lists of variable arguments, but because we are in C, you must cast your arguments (especially int as some Arduino boards use uint16_t and others uint32_t) properly before sending them.
To send a list, use sendMessage():
void sendMessage(const char *address, const char *format, ... );The format string defines the argument type of each following argument. You must provide a number of arguments depending on each argument type:
- "i" : one
int32_t - "f" : one 32-bit
float - "s" : one pointer to a null terminated char array
- "b" : one pointer to a
uint8_tarray followed by itsint32_tlength - "m" : one pointer to a
uint8_tarray of size 4
Example that sends a "/stuff" message with a float, string and integer arguments:
myOsc.sendMessage("/stuff", "fsi", (float) 1.0 , "hello", (int32_t) 2);Example that sends a "/blub" message with a blob argument:
uint8_t blob[4] = {1,2,3,4};
uint32_t length = 4;
myOsc.sendMessage("/blub", "b", blob, (int32_t) length);MicroOsc contains two core classes:
-
MicroOsc
The main OSC interface used to send and receive OSC messages. It handles message encoding, transport handling, bundle parsing, and dispatching received messages. -
MicroOscMessage
Represents a single received OSC message. It provides methods to inspect the OSC address, verify argument types, and sequentially read message arguments.
| MicroOsc Method | Description |
|---|---|
void parseMessages(MicroOscCallback callback, unsigned char *buffer, size_t bufferLength) |
Parses OSC data contained in buffer and calls callback once for each received message. Supports bundles and single messages. |
void parseMessages(MicroOscCallbackWithSource callback, unsigned char *buffer, size_t bufferLength) |
Same as above but also passes the MicroOsc instance to the callback. |
Address and arguments types:
| MicroOscMessage Method | Description |
|---|---|
bool checkOscAddress(const char* address) |
Returns true if the OSC address matches exactly. |
bool checkOscAddressAndTypeTags(const char* address, const char* typetags) |
Returns true if both the OSC address and type tags match exactly. |
MicroOscMessage reads arguments sequentially. Each call to a method that starts with the words nextAs advances the internal read pointer to the next argument. Arguments must be read in the same order as they were sent.
| MicroOscMessage Method | Description |
|---|---|
int32_t nextAsInt() |
Returns the next argument as a 32-bit integer. Advances the internal read pointer. |
float nextAsFloat() |
Returns the next argument as a 32-bit float. Advances the internal read pointer. |
double nextAsDouble() |
Returns the next argument as a 64-bit double. Advances the internal read pointer. |
const char* nextAsString() |
Returns the next argument as a null-terminated string pointer. Advances the internal read pointer. Returns NULL if buffer bounds are exceeded. |
uint32_t nextAsBlob(const uint8_t **blobData) |
Returns the next argument as a blob. Fills blobData with the pointer to raw data. Returns blob length or 0 if error. Advances the internal read pointer. |
int nextAsMidi(const uint8_t **midiData) |
Returns the next argument as a MIDI message (4 bytes). Fills midiData with the pointer to raw MIDI bytes. Returns 4 on success, 0 on error. Advances the internal read pointer. |
Address and arguments types:
| MicroOscMessage Method | Description |
|---|---|
const char* getTypeTags() |
Returns a pointer to the type tags of the message. Valid only until the next received message; do not store it. |
const char* getOscAddress() |
Returns a pointer to the OSC address. Valid only until the next received message; do not store it. |
bool checkOscAddress(const char* address) |
Returns true if the OSC address matches exactly. |
void copyAddress(char* destinationBuffer, size_t destinationBufferMaxLength) |
Copies the OSC address into a user-provided buffer with maximum length. |
void copyTypeTags(char* destinationBuffer, size_t destinationBufferMaxLength) |
Copies the type tags into a user-provided buffer with maximum length. |
Parsing the buffer is done automatically with MicroOsc and an internal MicroOscMessage. But if you create your own MicroMessage, you can manually parse a custom buffer.
| MicroOscMessage Method | Description |
|---|---|
int parseMessage(unsigned char *buffer, size_t bufferLength) |
Parses an OSC message from a buffer. Returns 0 on success, negative value on error. The buffer is not copied. |
All sending functions internally write the OSC address, type tags, arguments, and handle padding according to the OSC specification. Messages are only sent if the transport is ready.
| MicroOsc Method | Description |
|---|---|
void sendInt(const char *address, int32_t i) |
Sends a single integer OSC message. |
void sendFloat(const char *address, float f) |
Sends a single float OSC message. |
void sendString(const char *address, const char *str) |
Sends a single string OSC message. |
void sendBlob(const char *address, unsigned char *b, int32_t length) |
Sends a single blob (array of bytes) OSC message. |
void sendDouble(const char *address, double d) |
Sends a single double OSC message. |
void sendMidi(const char *address, unsigned char *midi) |
Sends a single MIDI OSC message (4 bytes). |
void sendInt64(const char *address, uint64_t h) |
Sends a single 64-bit integer OSC message. |
void sendImpulse(const char *address) |
Sends an OSC impulse message (no arguments, type tag I). |
void sendTrue(const char *address) |
Sends an OSC boolean true message (type tag T). |
void sendFalse(const char *address) |
Sends an OSC boolean false message (type tag F). |
void sendNull(const char *address) |
Sends an OSC nil message (type tag N). |
void sendMessage(const char *address, const char *format, ...) |
Sends an OSC message with multiple arguments. The format string defines argument types using OSC type tags. |
These methods allow manual construction of an OSC message by explicitly starting and ending it. The argument count and types must be managed manually; MicroOsc does not perform type or count checking.
| MicroOsc Method | Description |
|---|---|
void messageBegin(const char *address, const char *format) |
Begins a new OSC message. Writes the OSC address, and writes the type tag string (format). After this, use one or more messageAdd*() methods to append arguments. These calls must match the format. |
void messageEnd() |
Ends the current OSC. This finalizes and sends the message through the active transport. |
Methods for adding arguments to a message:
| MicroOsc Method | Description |
|---|---|
void messageAddInt(int32_t value) |
Appends a 32-bit integer argument in big-endian format. |
void messageAddFloat(float value) |
Appends a 32-bit float argument in big-endian format. |
void messageAddDouble(double value) |
Appends a 64-bit double argument in big-endian format. |
void messageAddString(const char *str) |
Appends a string argument. The string is null-terminated and padded to a multiple of 4 bytes. |
void messageAddBlob(unsigned char *data, int32_t length) |
Appends a blob argument. Writes the length as a 32-bit integer, followed by the raw data, then pads to a multiple of 4 bytes. |
void messageAddMidi(const unsigned char *midi) |
Appends a 4-byte MIDI argument. |
void messageAddInt64(uint64_t value) |
Appends a 64-bit integer argument in big-endian format. |
The following type tags are supported when sending or receiving messages:
| Type Tag | Description |
|---|---|
i |
32-bit integer |
f |
32-bit float |
d |
64-bit double |
s |
String |
b |
Blob |
m |
MIDI message (4 bytes) |
h |
64-bit integer |
T |
Boolean true (no argument data) |
F |
Boolean false (no argument data) |
N |
Nil (no argument data) |
I |
Impulse (no argument data) |
- OSC data is encoded using big-endian (network byte order).
- Arguments must be read in the same order as defined by the type tags.
- Returned pointers from
nextAsString(),nextAsBlob(), andnextAsMidi()refer to internal buffers and must not be modified or stored beyond the lifetime of the message. - Bundles are automatically detected and unpacked. Each contained message triggers the callback individually.