Library / SDK
CNMAT/OSC avatar
CNMAT/OSC

OSC for Arduino corrects the RGBA and MIDI wire format in version 4.0.0

OSC: Arduino and Teensy implementation of OSC encoding

798 stars146 forksMaxNOASSERTION

At a glance

What is it?
The CNMAT OSC library brings Open Sound Control encoding to Arduino and Teensy boards across serial, Ethernet, WiFi and TCP. Version 4.0.0 changes bytes on the wire for colour and MIDI messages, so both ends of a link must move together.
Who is it for?
Adopt OSC for Arduino when a board needs to speak Open Sound Control without a host stack, especially if you are starting fresh on the 4.0.0 wire format. Do not adopt it for a link that must talk to software still speaking the 3.5.8 byte order, and check the LICENSE file first because the terms are custom rather than a standard open source licence.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 6 days ago.
What is it written in?
Mainly Max, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Open Sound Control encoding that fits in a microcontroller

OSC is a small UDP-friendly protocol, and the library in this repository implements its encoding for boards with kilobytes of RAM rather than desktop machines. It was developed primarily by Yotam Mann and Adrian Freed at CNMAT, where OSC was invented, and the README lists contributions from John MacCallum, Matt Wright, Jeff Lubow and Andy Schmeder among others.

The type coverage is wider than the bare minimum. The four basic OSC types are supported, meaning 32-bit integers, 32-bit floats, strings and blobs of arbitrary length. On top of that sit the optional 64-bit timetag, 64-bit integers, Booleans, Null and Impulse, RGBA colours and MIDI messages, plus address pattern matching and dynamic memory allocation. Sixty-four bit doubles are supported where the target has them, and the README is explicit about the AVR case: double is 4 bytes on AVR, so there the value is encoded as a 32-bit float 'f' instead of 'd'. That single sentence decides whether a numeric value survives a round trip on older 8-bit boards.

Colour and MIDI bytes were reversed until 4.0.0

The headline change in 4.0.0 is that the library puts different bytes on the wire. Up to and including 3.5.8, the 'r' colour type and the 'm' MIDI type were encoded and decoded byte-reversed, and 'm' dropped its data2 byte, the MIDI velocity, while transmitting a channel byte that the OSC 'm' type has no slot for. Because the encoder and decoder were reversed symmetrically, a link between two CNMAT boards round-tripped correctly, and the fault stayed invisible unless you talked to other OSC software, with which these two types could never interoperate.

The comparison the project documents is short enough to keep in mind: a colour {0x11, 0x22, 0x33, 0x44} went out as 44 33 22 11 under 3.5.8 and goes out as 11 22 33 44 under 4.0.0. A MIDI message {1, 0x90, 2, 0x3C, 0x40} changed from 3c 02 90 01 to 01 92 3c 40. Both ends of a link must be updated, and that is the practical cost of the release.

MIDI messages lose their separate channel slot

The MIDI type needed more than a byte reversal, because OSC's 'm' type has five fields with no room for a channel. The oscmidi_t struct keeps its five fields, so existing sketches still compile after the upgrade, but the meaning of one of them changed. The channel is now folded into the low nibble of the status byte when the message is encoded. Setting the channel in status directly and leaving channel at 0 works as well.

That is a subtle migration. A sketch that previously passed a channel of 3 alongside a status of 0x90 still compiles and still type checks, but the value that reaches the wire has moved. Anyone porting a 3.5.8 sketch to 4.0.0 has to decide deliberately whether to keep passing channel and let the library fold it, or to encode the channel into status themselves. The API reference is where that decision is documented, at the oscmessage.addoscmidi_t entry in API.md.

Bundle timetags now mean immediately, and survive a forward

Two bundle timetag fixes also land in 4.0.0. The first concerns the default: a bundle built without an explicit timetag now sends 0x0000000000000001, the value OSC 1.0 reserves for immediately, rather than 0x0000000000000000, which means the date 1900-01-01:

yaml
0x0000000000000001
0x0000000000000000

Code that depends on the old default can pass zerotime explicitly to get it back.

The second is about forwarding. The setTimetag(uint8_t *) function read its 8 bytes with a raw memcpy, so on a little-endian host a bundle that was received and then forwarded came back out reversed: a timetag of 0x0000000000000001 was re-sent as 0x0000000001000000. It now reads the buffer in OSC wire order. For anyone running a bridge that receives bundles and passes them on, this is the change that makes timestamps mean the same thing at both ends of the link instead of only inside one process.

Transports stay outside the library

The library itself pulls in no networking header. Every sketch meets it at the same two calls: send() writes to anything deriving from Arduino's Print class, and fill() takes bytes from wherever they arrived. The transport is the only thing that changes between examples, and the examples folder is grouped by transport to make that visible. Serial sketches carry OSC over USB or hardware serial framed with SLIP, and Ethernet examples such as UDPSendMessage, UDPReceive, UDPCallResponse and UDPOscuino use the Ethernet library with a Wiznet shield or equivalent, while ETC_EOS_TCP runs OSC over TCP rather than UDP, SLIP framed over an EthernetClient, talking to an ETC Eos console.

Because Print and Stream are the only contract, a new transport means writing a class that satisfies one of them, not patching the encoder. That is also why the source tree splits into OSCMessage, OSCBundle, OSCData, OSCMatch, OSCTiming, SLIPEncodedSerial and SLIPEncodedTCP rather than mixing transport code into the parser.

One address space named by capability, not by board

Every sketch, firmware and web page in the repository speaks the vocabulary defined in ADDRESSES.md, and the convention is capability based. A client asks /enq, the board answers with its name and one /enq/<capability> line per thing it actually has, and from then on /rgb, /display/text, /buzz, /btn and /imu mean the same thing everywhere. A stream is a bundle of /state <seq> <millis> plus one message per capability, and nothing is streamed under a board's own name. The capability names a client can expect look like this:

yaml
/enq
/rgb
/display/text
/buzz
/btn
/imu

That design is what lets a single page drive any board over Web Serial, Web Bluetooth or HTTP, with extras/webserial/oscuino.html building its panels from the /enq lines it is told about. It also replaces a per-board convention such as /pybadge, /cpx, /xb and /egg that spelled the same idea four different ways with a page per board. Drift is guarded by a test: extras/webserial/test/test-namespace.mjs fails on any address outside the contract, so an out-of-contract address cannot return quietly.

Python firmware as the alternative to a compiled sketch

There is a real alternative inside this project, and it changes the language rather than the protocol. The same wire contract now has firmware for boards that run Python instead of compiled sketches: MicroPython on the BBC micro:bit and CircuitPython across Adafruit's boards live in extras/python, each with the same style of Web Serial page:

extras/python The README is blunt about the split: the protocol is the product, and the Arduino library is one implementation of it.

For a board you already program in Python, that path avoids a compile step and avoids the AVR limitation on 64-bit doubles. For an Arduino or Teensy target, the C++ library is the only implementation, so the two cannot be treated as interchangeable at the source level even though they interoperate on the wire.

The board-specific demos also split along the same line. EsploraOscuino and CircuitPlaygroundSensors are hand written rather than generated from extras/webserial, so make check does not see them. The Circuit Playground pair has its own test that pins the sketch and the page to the same capability messages, and make test in extras/webserial runs it.

Installing on boards that Arduino IDE 1.8.5 could not target

Installation goes through the Arduino Library Manager with a current Arduino IDE (2.x) or arduino-cli. The project documents no build script of its own, so the toolchain is the one you already use for sketches:

bash
arduino-cli

The reason for the version requirement is specific: the platforms this release targets ship as Boards Manager packages that 1.8.5 predates. UNO R4, ESP32 and RP2040 are listed as verified on hardware, while nRF52 and UNO Q are compile tested only. Teensy support comes from the Teensy Boards Manager package for Arduino IDE 2.0.4 or later, not the older Teensyduino overlay.

That distinction between verified on hardware and compile tested only is worth reading before you commit to a board, since the library assumes several platform-specific behaviours: the AVR double size, and the endianness handling that the timetag fix addresses. Board coverage and bring-up notes live in BOARDS.md and BRINGUP.md, while ADDRESSES.md and API.md cover the message contract.

On maintenance, the last push was on 2026-10-17 and release 4.0.0 was tagged on 2026-08-04. The gap before it is wide: the previous tag, 3.5.8, dates from 2023-09-04, so anyone pinned to 3.5.8 is looking at a wire format that a CNMAT-only link could hide but any other OSC implementation would expose.

Licensing is the other thing to settle before you build on this. The project does not carry one of the licences GitHub can classify automatically, so the terms are in the LICENSE file in the repository root and have to be read there. That matters most for anyone shipping the library inside a product, and it is the first file to check, ahead of any of the board notes.

Editorial conclusion

Adopt OSC for Arduino when a board needs to speak Open Sound Control without a host stack, especially if you are starting fresh on the 4.0.0 wire format. Do not adopt it for a link that must talk to software still speaking the 3.5.8 byte order, and check the LICENSE file first because the terms are custom rather than a standard open source licence. Before writing code, confirm your board is in the supported list in BOARDS.md and read the API.md entry for oscmessage.addoscmidi_t, since the channel field now folds into the status byte.

Frequently asked questions

Which boards does the OSC for Arduino library support?

The platforms this release targets are UNO R4, ESP32 and RP2040, all verified on hardware, plus nRF52 and UNO Q, which are compile tested only. Teensy is supported through the Teensy Boards Manager package for Arduino IDE 2.0.4 or later rather than the old Teensyduino overlay.

What changed in version 4.0.0 of the OSC for Arduino library?

The bytes on the wire changed for the RGBA colour type and the MIDI type, which were previously encoded and decoded byte-reversed, and both ends of a link must be updated. The release also makes a bundle without an explicit timetag send the immediate value, and stops received timetags being byte-swapped when a bundle is forwarded.

How do I send an OSC MIDI message after upgrading to 4.0.0?

The oscmidi_t type keeps its five fields, so existing sketches still compile. The OSC 'm' type has no separate channel slot, so the channel is now folded into the low nibble of the status byte when the message is encoded, and setting the channel in status directly while leaving channel at 0 also works.

Can I use OSC on a board without writing an Arduino sketch?

Yes. The repository ships MicroPython firmware for the BBC micro:bit and CircuitPython firmware for Adafruit's boards in extras/python, each with a Web Serial page. Those implement the same wire contract as the Arduino library, so a client sees the same protocol.

Official sources

  1. CNMAT/OSC on GitHub
  2. Issues
  3. README
  4. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/cnmat-osc.svg)](https://hysenlabs.com/projects/cnmat-osc)

Was this useful?