Embed
For developers embedding the machine in a host of their own, in C or C++.
- 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.
- 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. - The kit — what an embedding needs around the library: name lookup backed by a radix tree, the extended OSC types, and module loading.
- 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. - The hosts as worked examples, one per shape of embedding:
the command line loads modules with
dlopenat 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:
o.se.osen/osen.ose— the command line’s entry point: loads the module, the prelude, the REPLo.se.osen/prelude.ose— the host-neutral core of the osen standard libraryo.se.osen/repl-core.ose— the host-neutral REPL, embedded by the browser and Python hostso.se.osen/web.ose— WebAudio and clock wrappers for the browser hosto.se.osen/host/io.ose,host/proc.ose— host modules
osen — .osen:
o.se.osen/higher.osen,wrappers.osen— the standard library’s osen halfo.se.osen/host/load.osen—/o/load, which needs a filesystemmax.o.se/code/*.osen— the Max objects’ sources
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