Embed

For developers embedding the machine in a host of their own, in C or C++.

  1. The library — what it is, what it costs, and the one contract it asks of you: you hand it memory and it never asks for more. Then writing a host, below: what a host must do, and must not.
  2. Embedding libose, in the C reference — getting a bundle to work with and the overhead to size for. Then the four programs in libose/examples/, each with its source: create a bundle, a simple OSC server, a server that dispatches addresses and the environment as persistent storage.
  3. The kit — what an embedding needs around the library: name lookup backed by a radix tree, the extended OSC types, and module loading.
  4. The module contract — ose_main, loading, and why a module built against a different library is refused; then calling C from osen: what a call hands your function, and the wrapper that makes it an /o/ verb.
  5. The hosts as worked examples, one per shape of embedding: the command line loads modules with dlopen at run time; the browser has no dynamic loader and links its modules in; the microcontroller has no allocator worth the name and registers of a few kilobytes.

Writing a host

A host is a program with the machine inside it, and a way in and out. It must do three things: allocate the machine and its registers; get bytes in — whatever the outside world is, a socket, a Max inlet, a WebSocket, a serial line, the host puts them in the input register and runs the machine; and get output out, by printing, an outlet, or a callback. That is the whole obligation; everything else is optional capability, bound into the host register as addresses so a program can reach it.

A host must not do three things. It must not assume osen is loaded: osen is a module a user loads by choice, and a host that requires it has made it mandatory — binding names that only make sense with it is fine, depending on it is not. It must not bind into another’s register: a host or module owns its own register and puts things there; /_x is shared, and /o belongs to the language, so binding there overrides the language, which is occasionally what you want and never what you want by accident. And it must not grow: a host is a bridge, and logic about what programs mean belongs in the machine, where every host gets it.

A host without a capability simply does not bind its names. A program that calls /o/audio/start where there is no audio gets the unbound-name handler, which reports the missing name — better than a stub that silently does nothing. The name exists or it does not; there is no third state.

The module contract

A module is a shared library with one entry point, ose_main, that pushes one message per function it provides; a loader compares its ABI record with the host’s before handing over a bundle. The whole contract, with the static-linking path for hosts without dlopen.

Implementation — for embedders and module authors

Implementation

Which files are which

The instruction language and osen share a file tree, a prompt and a REPL, and the extension says which a file is: .ose is instructions, one message per line; .osen is the language, parsed into a bundle.

ose — .ose:

osen — .osen:

The REPL accepts osen and is written in ose: its job is to read osen, hand it to the parser, and execute the result. The layers meet three ways: an .ose file loads the osen runtime (osen.ose loads the parser module, then the prelude); an .osen file may open with instructions to bootstrap itself; and an .ose file may carry osen source as data, followed by the instructions that parse and execute it — prelude.ose does this for four wrappers:

/o/print : {["/!/unpack/drop", "/!/osen/format", "/!/print"]}
/!/osen/parse/raise
/!/drop
/!/exec1
/!/drop
/<</_x

The first line is osen, as data; the lines under it are the instructions that turn it into something.

Targeting one layer or both

Targeting the machine only needs libose and a host that supplies bytes: OSC addressing, the bundle-as-state invariant, and the instructions, with no parser — the right level for a microcontroller, where a host may give /@ and /$ the meaning write a pin and read a pin. Targeting osen as well adds the parser module and the prelude, at the cost of a parser and the memory to hold parsed programs; parsing expands rather than compresses, about 2.8 times in one measured case. Because a parsed program is a bundle, where parsing happens is a choice: parse on a workstation and send the bundle to a device with a machine and no parser, or send the source and let the far end parse it, which is smaller on the wire and legible in transit.


Start · The library · The kit · C reference · The hosts