Max
What it is
Two externals, o.se and o.se.gui, and a couple of dozen Max objects
written in the language itself and kept in code/: o.se.o.route,
o.se.o.pack, o.se.o.collect, o.se.o.if, o.se.o.var, the set
operations (o.se.o.union, o.se.o.intersection,
o.se.o.difference), and others. Each one named after an object in
CNMAT’s odot library, o.route, o.pack and the rest, is a drop-in
replacement for it, written in osen; the o.se. in front is only how
the loader finds it, so the two sets coexist in one Max installation.
An object is a program, so it can be read, changed and reloaded without
a compiler.
Get it running
Needs the Xcode command line tools and the Max SDK, which is vendored as
source/max-sdk-base; not Xcode itself.
git clone --recursive https://github.com/asomadev/max.o.se
cd max.o.se/source
make # modules, externals, then copies the .so files into ../misc
Inside a clone of the asoma workspace, the same host is
hosts/max.o.se: make -C hosts/max.o.se/source.
osen and the standard library are modules the objects load. make
LINKED=1 compiles them into the externals themselves instead, and
LTO=1 optimises that as one. Either way an object loads osen before
anything else and keeps its vocabulary for as long as it lives.
The build writes the two externals into externals/ and the modules
into misc/, and the clone is laid out as a Max package: externals/,
help/, patchers/, init/, and the objects in code/. Put the clone,
or a link to it, in the Packages folder of your Max, inside Documents,
and start Max. Nothing else on the machine is touched. Source and
releases: github.com/asomadev/max.o.se.
You have it when help/o.se.maxhelp opens and its [o.se] answers a
bang with the time.
First program
Open help/o.se.maxhelp in Max. It puts an [o.se] object in a patch,
sends it a message of instructions, and shows a bang making the object
answer with the time. An [o.se] object takes instructions as messages,
last to first; an object named o.se.<name> loads <name>.ose from
the search path when it is created, so o.se.o.route loads
o.route.ose.
What it adds
Into the host register, as /o/ verbs a program calls like the
language’s own:
/o/inlet/make,/o/outlet/make— an inlet or an outlet on the object./o/inlet/make/machine,/o/outlet/make/machine— the object’s inlet for a whole suspended machine, and the outlet that sends one out; one of each per object./o/outlet— a value sent out of an outlet./o/outletatoms— values sent out of an outlet as Max atoms./o/post—(x) -> nothing; x printed to the Max console/o/error—(x) -> nothing; x printed to the Max console as an error/o/gettext— the object’s text./o/addmsg—("maxname", "/osc/name") -> nothing; the object answers the Max message by calling the function bound at the osc name/o/addtypedmsg—("maxname", "/osc/name", types) -> the same, with Max argument types; /o/nil for none/o/def/msg/anything—(msg, inlet) -> nothing; a Max message of any name delivered to /FullPacket as a bundle/o/time/now—() -> the time now, as a timetag
And as instructions: /makeinlet, /makeoutlet, /outlet,
/outlet/left, /outletatoms, /post, /maxobj, and a second kind of
inlet and outlet for machines (/makeinlet/machine,
/makeoutlet/machine, /outlet/machine).
Functions across a cord, and across a network
A bound function is a pointer, and a pointer means something only in the
process that made it; it also carries the name it was bound under, and
the name is what can travel. Between o.se objects on a patch cord,
functions pass as they are. Anything that goes through a network, or
holds a packet for later, has to carry them as names, and four objects
mark that boundary: o.se.udpsend writes every pointer as its name and
sends; o.se.udpreceive checks an arrival with the wire’s rules and
refuses, and reports, one it cannot read; o.se.disarm writes pointers
as names after anything else from outside; o.se.rearm resolves them
here again, as a cache. A function written as its name works anyway: it
resolves where it is applied. o.se.serve runs whatever is wired to it,
so put o.se.udpreceive or o.se.disarm in front of it wherever a
network is involved.
A patch as a node
[o.se.node 7401] makes a patch a node on the network: it answers the
census, takes loops dropped on it from the view or sent from a
prompt, and runs them on a Max clock, four at a time. Inside it is
o.se.world with a receiver and two senders, one to the census and one
for loops sent on; [o.se o.se.world /@/here/port 7401 /@/here/id
kitchen] is the object on its own. It takes OSC only, so a Max message
is the message of the same name: /!/continue/hold /blink,
/!/continue/now /blink, /!/continue/release /blink,
/!/continue/list, /!/continue/stop /blink, /!/continue/bind /blink
33 /blink/blue; and bang runs one period of every loop, for a metro.
A name a loop calls that nothing binds goes out of the first outlet as a
message, /rgb/pulse 0 150 0 300; a name it reads goes out of the
second, and the rightmost inlet answers the read while the call is going
on: an int, a float, a list, or a packet from o.se.o.pack. The patch
keeps the value, in a [f] or a [zl.reg]; the object keeps nothing. A
loop leaving for another node goes out of the third outlet as a machine,
host and port first for [udpsend], or by /forward to
udp://host:port.