asoma
An anarchic society of machines.
Connecting machines is easy; getting them to behave as one is the work. Asoma is a small virtual machine and a language for it, built so that the boundary between machines matters less.
Anything with the machine in it — a laptop, a browser, a board, a Python process — offers what it can do as addresses, and what it does is a program: written anywhere, sent to it, composed from several machines at once, replaced without a compiler. A program is a bundle of OpenSoundControl (OSC) messages, and so is the running machine itself — its values, its names, the rest of its program — so a running program can be sent too, and carry on where it lands.
The language
Each box below holds a live machine, compiled to WebAssembly. Every line you write is a message to a local OSC server: the machine inside the box, on this page. Edit the program and press Run.
A message is an address and a value. Parentheses gather messages into a
bundle, a colon binds a value to a name, and /o/get reads a message
back out of a bundle by its address:
# a bundle of messages, bound to a name /voice : (/freq : 440, /amp : 0.5), /voice, (/voice, "/freq") /o/get # → : ( # → /freq : 440, # → /amp : 0.5 # → ) # → : 440
Each line answers with a message of its own; the leading colon marks one
with no address. Commas separate the lines of a program, and # starts a
comment.
A call puts the arguments first and the function after them, as if the bundle of arguments were sent to the machine and then the function to apply to it:
/freq : 440, # arguments first, then the function /octave : (/freq, 2) /o/mul, (/freq, /octave) # → ( # → : 440, # → : 880 # → )
A function is a body in curly braces — a bundle kept for later rather
than run now — and the names of its parameters. /o/defn puts them
together, and the result is called like anything built in:
# a body kept for later, and its parameter
/octave/up : ({ (/hz, 2) /o/mul }, "/hz") /o/defn,
(/freq : 220, /octave : (/freq) /octave/up)
# → (
# → /freq : 220,
# → /octave : 440
# → )
That function is itself a bundle of messages: the body’s instructions
and the parameter list. /o/inspect shows them:
/octave/up : ({ (/hz, 2) /o/mul }, "/hz") /o/defn,
/octave/up,
# the function, as the messages it is made of
(/octave/up) /o/inspect
# → : <fn (/hz)>
# → : ": {
# → /_i : {
# → : [ "/!/os/g", "/$/hz", 2, "/o/mul", "/!/os/C2" ]
# → },
# → /_p : "/hz"
# → }"
And so is the machine’s own memory. />/_e puts the environment on the
table: every name this program has bound, as one bundle. This is what
would travel if the program moved to another machine. (The two empty
bundles are the scopes the running line opened; how osen
evaluates explains them.)
/freq : 440,
/octave/up : ({ (/hz, 2) /o/mul }, "/hz") /o/defn,
# the machine's memory: everything bound so far
/>/_e
# → (
# → /octave/up : <fn (/hz)>,
# → /freq : 440,
# → (),
# → ()
# → )
The guided tour of osen goes on from here — choosing, lists, folding, and how names are found.
Where it runs
The same machine runs in each of these. Any of them can send a program to any other but a browser to a browser, and the command line, the board and the browser can send each other a running machine.
- Command line — a prompt, files and UDP; the easiest to start with.
- Max — two externals, and Max objects written in the language itself.
- Browser — WebAssembly, WebAudio and a WebSocket; the boxes above, and a playground.
- Microcontroller — Arduino-core firmware for the ESP32-S3, the Teensy 4.0, the Pico, the Mega and more; pins, WiFi and the serial cable.
- Python —
import ose, and a module that lets the machine call Python.