PythonTap
Write Max objects in Python. PythonTap embeds a CPython interpreter in a Max package and runs a Python class as a Max object, in one of two ways:
tap.python~runs the class as an audio object: itsprocess()method is called on the signal — once per signal vector with numpy arrays, or once per sample with floats.tap.pythonruns it as an ordinary object, without audio: messages in, messages out — and what a method returns is what the object outputs.
Either way:
- The class's type-annotated attributes become Max attributes (
@gain 0.5in the object box,gain 0.5,getgain, attrui — it all works). - The class's public methods become Max messages, called according to their signatures, with arguments converted according to their type hints.
- The source file is watched and hot-reloaded every time you save it, keeping attribute values, so you can live-code with Max running.
- Python's
print()output and tracebacks land in the Max console, posted from Max's main thread whatever thread printed — and no exception your code raises,sys.exit()included, can take Max down.
from attrs import define, field
@define
class default:
"""The class loaded by tap.python~ and tap.python when no source argument is given.
Demonstrates the mapping from Python to Max:
- the annotated 'gain' field becomes a Max attribute
- any public method (like 'greet') becomes a Max message
- methods named 'int' and 'float' answer those standard Max messages
- process() runs on the audio signal in tap.python~, once per sample (see
numpy_gain.py for the much cheaper once-per-vector form)
- in tap.python, what a method returns is output: bang outputs the gain
"""
gain: float = field(default = 1.0)
def greet(self, name: str) -> None:
print(f"hello {name}, from python!")
def process(self, x: float) -> float:
return x * self.gain
def bang(self) -> float:
return self.gain
# These two come last on purpose: once `def float` has run, the name `float` in
# the class body means this method, so an annotation written after it would no
# longer mean the built-in type. (Inside method bodies the built-ins are unaffected.)
def int(self, new_gain: int) -> None:
self.gain = float(new_gain)
def float(self, new_gain: float) -> None:
self.gain = new_gain
[tap.python~ default] ← loads python/default.py: a 'gain' attribute, process() on the signal
[tap.python] ← the same file, without audio: a bang outputs the gain
The package comes with CPython 3.13, numpy and attrs, on macOS (Apple Silicon and Intel) and Windows, for Max 9.
This book
- Installing the package, and adding Python packages to its runtime.
- Writing a class: the file, attributes, messages, hot reload — what is the same
for both objects. Then Audio and Worker mode for
tap.python~, and Objects without audio fortap.python. - Performance, measured, and Errors and limits: what the objects guard against and what they cannot.
- The examples in the package's
pythonfolder, and a notebook that runs one of them the way the object does. - For contributors: Building, Testing and the design.
In Max itself, the package's guide Writing Max Objects in Python and three tutorials are in the Documentation window (Package Docs › PythonTap), and PythonTap Overview is in the Extras menu.