libose

An implementation of OpenSoundControl in C89, and a small virtual machine whose entire state is a valid OSC bundle.

It allocates nothing. You hand it a block of memory and it works inside that block, which is what makes it usable on a microcontroller, inside an audio callback, or anywhere else allocation is unavailable or forbidden.

char bytes[65536];
ose_bundle bundle = ose_newBundleFromCBytes(sizeof(bytes), bytes);
ose_pushMessage(bundle, "/hello", strlen("/hello"),
                1, OSETT_STRING, "world!");

Why the VM

The interesting part is not the OSC encoder. It is that the machine’s whole state — stack, environment, control, dump — is itself an OSC bundle. A program is therefore data, and data can be sent somewhere else and run there. A workstation can build a program and hand it to a device that has a VM and no parser.

Nothing outside the bundle, ever

That is a rule, not a description: libose keeps no state of its own. No file-scope or function-static variable written at run time, for any reason, behind any option: no table, cache, counter, callback or flag. Everything lives in a bundle you handed it. The same holds for osekit.

It is what lets two copies of the library sit in one process (two Max externals, a plugin beside o.se.modloader) and lets a module loaded once call either. The asoma workspace’s tools/checks/check_globals.py compiles libose and osekit and fails on any writable global.

Building

make                    # -> build/default/libose.a
make BUILD_ID=myhost    # -> build/myhost/libose.a
make check-build        # prove a header change reaches every object
make doc                # doxygen -> doc/doxygen/html

Output is written to build/$(BUILD_ID)/ rather than beside the source, because libose is meant to be vendored and every host that embeds it builds it with different options — different VM sizes, different types, different hooks. One source tree can carry every host’s artifacts at once. A .flags stamp in each build directory records the configuration, so changing a -D rebuilds instead of quietly mixing incompatible objects into one archive.

If you embed it, pass BUILD_ID and read the archive back from $(LIBOSEDIR)/build/$(BUILD_ID)/libose.a.

Testing

make -C test/ose_test check

Twelve suites. The harness builds its expected values with preprocessor macros rather than with the library, so a bug in libose cannot make a test agree with it.

ut_ose_preconditions is built deliberately without OSE_CONF_DEBUG: it asks what the library does when a precondition is violated and there is no assert to catch it, which is the configuration that ships.

Dependencies

None beyond the C standard library — string.h, stdio.h, stdlib.h, math.h, inttypes.h, stdarg.h. arpa/inet.h and windows.h appear only for byte-order helpers behind platform guards.

Documentation

make doc generates the API reference and a set of conceptual pages from doc/pages/. Start with:

Where this sits

libose is the bottom of a stack. Above it: osekit (name lookup, extended types, module loading), o.se.stdlib (the C standard library bound into the VM), and o.se.osen (osen, the language people write). Hosts — a command line, a browser, Python, Max — sit on top of those.

Nothing in libose knows a host exists.

Licence

Licensed under either of

at your option.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.