Overview
Messages and bundles
An OpenSoundControl message is an address and some values: /freq 440.
A bundle is a list of messages and bundles. Everything here is one or
the other: a value is a message, a program is a bundle, a function is a
bundle stored under a name, and so is the machine that runs them.
In OSC a bundle’s elements have no names, so a bundle with a name is a
message whose payload holds the bundle as a blob, and the message
carries the address. That is what a register is, and why
/_s is an address. The same shape has a typetag of its own inside a
message, |, a bundle laid out like a blob.
The machine and its registers
A machine is five registers — bundles with names, inside the machine’s own bundle:
- the stack,
/_s: operands and results - the environment,
/_e: the names a program has bound - the control,
/_c: the instructions running now - the dump,
/_d: saved contexts, for returning from a call - the input,
/_i: what is waiting to run
Those five are what a program runs on, and what travels when a machine
moves. libose adds two beside them: a lookup cache, /_0, and the fault
register, /_h, which holds why the machine last stopped. Every other
register belongs to the host or module that made it, and says so in its
name. The host register, /_x, holds what the host bound — its
capabilities, as addresses. osen keeps its own instructions, the ones a
program’s bundle is lowered into, in /os, and on the hosts that load
it the reference as data in /_q, one entry per name.
A name is looked up in the register its first segment names, then in that register of each machine outside this one, then in the environment, then in the host register; the first place that has it answers. An inner machine that finds nothing asks its outer.
Because every register is a bundle, the whole machine is one valid OSC bundle at every moment; nothing is serialised on the way out because nothing was ever in another form. Hosts and modules can add registers of their own. The registers has the naming rule and how an address’s first segment selects one; the C reference’s machine page has the run loop.
Two languages
osen is the language people write; the machine runs ose, its
instruction language, one message per instruction, and osen is a module
that turns source into it. Every host runs both. Side by side:
ose |
osen | |
|---|---|---|
| what it is | the machine’s instruction language | the language people write |
| made of | OSC messages, one per instruction | OSC bundles with a syntax |
| bind a name | /@ |
: |
| read a name | /$ |
the bare name |
| call | /! before an address |
(args) /o/fn |
| comment | /#/ |
# |
| separator | one message per element | , — a newline is whitespace |
| files | .ose |
.osen |
The same thing, twice. Binding 10 to /foo and reading it back, in
ose — three instructions, each an OSC message:
/,/i/10 push the integer 10
/@/foo assign it to /foo
/$/foo look /foo up again
and in osen:
/foo : 10,
/foo → : 10
The osen form is not shorthand for the other. It is source text that the
osen module turns into a bundle, which the machine then runs; /foo : 10
never becomes an instruction, it becomes a bundle. An analogy that fits:
ose is to osen roughly as assembly is to C. Everyone porting the machine
needs the instruction set written down; almost nobody writing programs
looks at it.
Addresses are the interface
A host binds what it can do — pins and WiFi, WebAudio, inlets and outlets, Python functions, UDP and files — into its host register as addresses. A program calls a capability on another host the way it calls one on its own. What each host binds: command line, Max, browser, microcontroller, Python.
What travels
A program is a bundle of messages the machine has not run yet. A running machine is a bundle holding its registers, the unrun program among them. Either can be sent. A program that arrives is run there; a machine that arrives continues where it was, and finds the far host’s capabilities in that host’s own host register, which stays where it is.
A register a program makes carries two flags, /flag/local and
/flag/temp, read and set by name:
(1024, "/xx") /o/make/register, ("/xx") /o/register/flags
( /flag/local : 0, /flag/temp : 0 )
A register marked local stays behind when the machine leaves, as the host register does; setting the flag answers how many of the names it was given the host understood:
(1024, "/xx") /o/make/register, ("/xx", (/flag/local : 1)) /o/register/flags/set
: 1
How each host takes what you send, and what carries it: Between hosts.
Loops
A program can ask the host to run a body again and again:
/n : 0,
({ /n : (/n, 1) /o/add, (/n) /o/println }, 200, "/count") /continue/every
The body runs every 200 milliseconds in a machine of its own, and the
caller’s environment becomes that machine’s state, so /n counts up from
one period to the next. Each period is due a whole number of periods
after the first, so a loop keeps its beat however long a body takes. The
name is optional; a loop sent again under the same name replaces the one
running, so an edited program leaves one loop, not two.
("/count") /continue/stop ends it and answers how many loops of that
name stopped; () /continue/stop ends the one due soonest. Inside a
body, (500) /continue/after pauses the loop for that long and then
finishes the period.
A loop is a value the host keeps in a slot of its queue, and the slot reads like anything else in a register:
/q0/name
: "/count"
/q0/every
: 200
() /continue/list answers one bundle per slot: the loop’s name, its
period, its size, and its state, waiting, running, held or
parked. ("/count") /continue/hold makes a loop run only when
("/count") /continue/now says so, and /continue/release gives it its
clock back. ("/count", "host:port") /continue/copy sends a copy of the
loop, its state included, to another host, where it is filed under the
same name and carries on from its count; the original runs on here.
("/count", 33, "/step") /continue/bind binds a name in a running loop’s
own environment, read from its next period.
A loop whose period fails stops at the step that failed and is parked:
the slot keeps the machine as it was at the fault, with no due time, and
its state says so. A copy of it goes anywhere a machine goes; fixed, it
comes back with /o/machine/resume, and the loop carries on from the
step that failed with its count intact. Between
hosts shows the round trip, and the view
draws every node’s loops as cards.
The hosts that keep a queue say so on their pages.
The layers
A host is a program with the machine inside it, through the kit; a module is a library the host loads or links in, and what it provides lands in the machine as addresses.
Where this comes from
Asoma grew out of odot, a library of Max objects for OpenSoundControl made at CNMAT, the Center for New Music and Audio Technologies at UC Berkeley, where OpenSoundControl itself was made.
The ideas it sits beside: mobile code, in Telescript and Obliq; Erlang’s processes and its location transparency; the Smalltalk image, a running system saved as one thing; and durable execution, a program whose state outlives the process running it.