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:
- Embedding libose — allocation, and the overhead you must size for
- The Bundle as a Stack — the model everything else is built on
- The OSE VM — the machine, if you want more than a bundle
- Building and Configuration — the compile-time options
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
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
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.