The latest beta of Field has an exciting and experimental new plugin — the Max/MSP Plugin. It basically works the same way as the Processing plugin, and it essentially exists for the same reasons as well — we're more excited about getting Field to hijack another environment than we are about making Field compete with it.
Max/MSP makes a particularly tempting target for such a cross-enivromental raid. It has a large community, it's a old product and it's got a completely different philosophy to programming. It also helps that Java (Field's underlying platform) is poorly appointed in the sound department, Max's speciality.
Where our Processing Plugin lets Field talk Processing, the Max/MSP plugin let's Field talk to Cycling '74's popular and apparently invincible visual data-flow programming environment. Where the Processing Plugin lets you write code that magically is "inside" a Processing Applet (which just happens to be running inside Field), the Max/MSP plugin let's you write code that magically is "inside" Max/MSP (which is running just like it normally does).
You should note that Field is by no means the first foray into hybridizing Max/MSP and code. In particular the Field's Max Plugin owes its existence to Nick Rothwell of Cassiel.com, whose pioneering Jython in Max work provoked us into trying something similar in Field. If you are looking for rock-solid, standalone Python/Max integration, you should be looking there instead. We're inspired by this work here, but the bugs are all ours.
Is OpenEnded Group excited about Max/MSP? The jury is still out: we're certainly on the record as being suspicious of Max dominance of its corner of new media art; but we're enjoying meeting the environment on our own — that is Field's — terms. We're actively looking for feedback on the design of the Max Plugin from experienced Max users, expect the final design to change over the next few months. We have 10 days left on our free Max/MSP demo...
This page assumes familiarity with Max/MSP and Field.
The Max Plugin (& Jitter) system is covered in this downloadable tutorial.
A taste of where we are going. The screenshot to the right here is either going to get you excited about this new feature of Field or not :
What is going on here? A number of things.
Firstly that really is a Max patch "loaded" into Field. Field can load and draw .maxpatch
files. This is mainly cosmetic — Field certainly can't execute Max files. But it doesn't need to — after all we have Max for that. Field uses this display to let you draw connections between Field boxes (that have code in them) and Max boxes.
The boxes marked with fuzzy 'm' characters are Field boxes that are bridged to the Max environment. Executing code in them (with the keyboard, or with the mouse) actually executes code in a Max box instead.
It's quite a trip, and it has two parts:
Firstly, you could always send data to Max from Field using the OscPlugin but you end up having to a) pack and unpack things for your own home-made OSC-based protocol b) use Max's rather baroque objects for doing this packing and unpacking. We'll see below that using the technique of "sending the code to be executed" rather than "sending data to poke Max into doing something" means no protocols, and no complex Max patches that just parse and assemble OSC messages. It's just a more direct way of telling Max what to do.
Secondly it's more than this. Not only can you execute code that ends up sending messages to Max boxes, but you can dynamically write (and then rewrite) code that Max will call when a box receives messages. You make code that runs autonomously.
The two of these things taken together might transform the way that you think about and use Max (and Field for that matter).
Max/MSP supports the MXJ object which is a window onto a Java runtime from a Max box. This is Field's route in. But before we can go any further you have to tell Max where Field's Max support is so that it can see it. Max stores it's Java preferences in /Applications/Max5/Cycling '74/java/max.java.config.txt, open it in TextEdit and add:
max.dynamic.class.dir /Users/marc/fieldwork/newfield2/Contents/extras/maxfieldMXJ/bin
max.dynamic.class.dir /Users/marc/fieldwork/newfield2/Contents/lib/jython-complete.jar
Next you'll have to enable the maxfield.jar plugins in the plugin manager in Field and restart Field.
Field comes with two classes for you to run in the MXJ:
The first object — field.extras.max.MaxFieldRoot
— acts as the anchor and the communication gateway for Field into the Max world. You want one (and only one) of these live at any given time.
The second object — field.extras.max.MaxField
— is a box that Field can run code in. You can have as many of these as you like. You need to send a bang to your MaxFieldRoot
every time you add a MaxField
object.
Finally, in order to send things to it from Field we need to give the MaxField
box a name. We do this with the inspector:
We've called our MaxField
box hello
. This name is important — it's how we're going to get to it from Field. Take this moment to send a bang to MaxFieldRoot
.
Now we have a special box in Max, how to we get to it from Field? Back in the lovely grey world of Field, we create a box:
We'll also call it 'hello' — that name is important too.
To attach it to the box we called "hello" in Max, we'll mark it as being special, with the right click menu:
This gets us a fuzzy 'm' marker:
Now the two boxes are connected — the one in Field called 'hello' is bridged to the box in Max called (in the inspector) 'hello'.
Select the box and write:
print "hello max"
Execute this code any way you like — command-return, command-0, option-click on the box, option-click on the marker in the ruler, hit the box with the red time slider — and you'll see "hello max" printed at the bottom of the text editor.
Up until now you'd really have to take it on trust that anything special is going on, after all, print "hello max"
typically prints "hello max" — nothing new there. So let's do something more impressive. Firstly let's add some more Max:
Here we've added a float box to the first inlet of our MaxField
box and another float box to the first outlet.
Now we can write in Field and execute:
_.outlet(0, 3.2)
And sure enough:
What exactly happened? In the Max/Field environment "_"
refers to the Max box (a little like the way _self
refers to a vanilla Field box). _.outlet(0, 3.2)
sends '3.2' to the first (that is, the zero'th) outlet. Where's the documentation on this magic "_"
object? Well, most of it is already on your computer:
file:///Applications/Max5/java-doc/api/com/cycling74/max/MaxObject.html
"_"
refers to a MaxObject so you can do all the things to "_"
that you can do to them (and more, we'll see below).
If you get lost you should note that the bridge between Max and Field is so tight that Field's autocomplete works just fine:
If we add a print
box in Max, we can write:
_.outlet(0, "hello max")
And get:
In the "Max window" we see two lines — the second is the float box complaining about someone sending "hello max" to it, but the first is the print box doing it's job.
It might be obvious, but we need to mention it here: you are not restricted to one-line Python programs. You have all of Python at your disposal and you can execute it in little bits or large chunks; you can import
libraries, you can import your own Java class files out of jars and so on. It's a real programing language in a real programming environment embedded directly inside Max.
So far Field has sent code to Max it has been executed immediately and basically forgotten. But we can send code that "pieces together" the object "inside" the Max box — allowing it to do things when it receives messages from other Max boxes without any intervention by Field.
Field has exactly the same architecture to address the problem of doing things in response to mouse events in the Processing Plugin — the idempotent callback decorator.
Don't worry about the name, here's the code:
@_._float
def addAndMultiply(inlet, f):
print("float %f " % f)
_.outlet(0, f*2+10)
Executing that code once means that from now on every time a float is received by the associated MaxField
box the addAndMultiply function will be called. Field will also get something printed to it's text editor. This code multiplies the incoming float by two and adds ten then sends it back out again. This means that we can wiggle the input float box in Max and see a response:
While there are other ways of writing simple expressions and putting them in boxes in Max, our way here allows us to use a real programming language — Python — with real library support (from Python and Java) and construct this "expression" interactively.
What if we change our mind about that 10 and prefer 11? You just edit the code and execute it again — you can only have one function with any given name registered to a particular callback at any given time, the previous definition is automatically erased.
What you can do is have multiple functions with different names all hanging off of _float
. Take a look at the docs for event hooks here for more information about this pattern.
Right now Field has callback decorators available for _int
, _float
, _bang
, _doubleClick
, _listAtom
, _listInt
, _listFloat
. That should hopefully cover things for a while.
Moving code around between Field and Max is very cool. But sometimes you want to do something that's simpler and easier to understand — moving data between Max and Field. You could always use Max's OSC support and marry that with Field's OSC support. But we can so something more integrated as well.
First two ways to send things to Max. The first is to ask the Max Plugin to do it for you. From any non-Max Field box:
_self.maxPlugin.sendData("hello", "pear", 5)
_self.maxPlugin.sendData("hello", "peach", "strings work as well")
This shows you how to send numbers and strings to Max boxes. You can also use this syntax:
hello = _self.find["hello"][0] # find the first box called "hello"
# properties ending in "_toMax_" are automagically sent over to the corresponding box in max
hello.pear_toMax_ = 10
hello.peach_toMax_ = "strings still work"
How to read this data from inside a Max Box? Simple:
print _.pear
print _.peach
Things sent over appear as attributes inside "_
".
Finally, you might be thinking — how do we send things back? From inside the Max Box:
_.somethingElse = "hello again from Max"
And from inside Field (a non Max Box)
hello = _self.find["hello"][0] # find the first box called "hello"
print hello.somethingElse_fromMax_
Using techniques from TopologyAndControlFlow we can even start reinventing Max paradigms inside Field:
We're still writing this page — we haven't mentioned how to 'load' Max files like the opening screenshot or run sequences based on generators inside Max; we're also hoping to have a nice example of a time-line in Field driving Max. We also need some fundamental information concerning how shared the namespaces between MaxField
boxes are.
Work in progress, comments (especially from Max specialists) appreciated.