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 scope markers; the tour 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 ESP32 and Photon 2 boards; GPIO and WiFi.
- Python —
import ose, and a module that lets the machine call Python.