Optional Exercise: Using Max with Python via OSC

This tutorial will introduce techniques for building a software system combining Python programs with Max patchers.

Objectives. At the end, you should be able to do the following:

  1. Extend an OSC messaging protocol used to connect a Max patcher bidirectionally with a Python program.

This tutorial assumes a good working knowlege of Max and Jitter, Python, numpy, UDP networking, and use of the command line.

Overview

The strength of Max is the interactive development environment; it is great for creating ad hoc user interfaces, live-coding performances, and easy access to media and graphics. Where it is weakest is traditional algorithmic programming and access to the accumulation of library code available in other languages.

One way to address this is to write externals, e.g. library of Max objects written in native code. This is a high-performance approach and has been used to wrap many libraries for use as native functions.

Another approach is to divide the system into components which communicate using networking. Since Max is a message-passing or data-flow programming system, treating interprocess communication as asynchronous message-passing is a natural architecture. The communications overhead introduces much more latency than embedding as an external, but the approach is very flexible and can also work across multiple machines as needed to harness computational resources.

This example presents a Python program which uses the commonly available Twisted framework and txosc package to send and receive messages to and from a Max patcher. This approach uses UDP packets formatted as OSC messages. A key property of UDP is that the connections are stateless, which in practice means that the Python program can be stopped and restarted without causing any connection problems; packets sent from Max when no receiver is active will just be silently dropped.

Getting Ready

  1. You’ll need a computer with the following installed: Python 2.7, Twisted, txosc, and numpy. These are pre-installed on the IDeATe cluster machines.
  2. Download and unpack max-osc-python.zip. You should now have a folder named max-osc-python with several files.
  3. Load the max-osc-python.maxpat patcher into Max.
  4. Open a command line (e.g. Terminal.app window) and start the Python program, e.g. python max-osc-python.py

Observations

  1. The toggle labeled ‘Enable frame clock’ will enable the patcher to start sending periodic data requests.
  2. The Python program will respond with a matrix of trajectory data, visible in the cellblock and plot~ objects near the bottom.
  3. Try changing the generator parameters to see how the curves vary.
  4. Try stopping and restarting the Python program.

Comments

The Python program uses an asynchronous programming style in which events generate callbacks. In many ways, this mirrors the internal nature of a Max patcher, in which objects communicate messages by calling other functions. The challenge is breaking down a computation into functions which respond to events within the context of persistent state.

In this example, the persistent state is provided by making all callbacks methods of the OscServer class. One instance of this class is created, and it configures its own methods as callbacks. The txosc package provides protocol classes which parse incoming UDP packets as OSC messages, dispatching them among the callbacks attached to the receiver object.

The example generates a numerical matrix and sends it back as a list of numbers. It uses numpy for efficient numerical calculation. A more meaningful example might perform calculations well-supported in Python, e.g. building and querying a machine learning model using scikit-learn and scipy.

(Still in progress.)