py2max
A pure Python library for generating Max/MSP patcher files (.maxpat, .maxhelp, .rbnopat) -- offline, and, via its js2max bridge, inside a running Max patcher.
If you are looking for Python 3 externals for Max/MSP, check out the py-js project.
Installation
pip install py2max
For the browser-based live editor and remote REPL, install the companion py2max-server package:
pip install py2max-server
For development:
git clone https://github.com/shakfu/py2max.git
cd py2max
make install
source .venv/bin/activate
Quick Start
from py2max import Patcher
p = Patcher('my-synth.maxpat')
osc = p.add('cycle~ 440')
gain = p.add('gain~')
dac = p.add('ezdac~')
p.link(osc, gain)
p.link(gain, dac)
p.save()
That's it! Open my-synth.maxpat in Max to see your patch.
Features
Core Capabilities
-
Offline Patch Generation - Create Max patches programmatically without Max running
-
Round-trip Conversion - Load, modify, and save existing
.maxpatfiles -
Max for Live (.amxd) - Read/write binary
.amxddevice files with presentation-mode helpers -
Universal Object Support - Works with any Max/MSP/Jitter object
-
Fully typed - Passes
mypy --strict; no runtime dependencies -
Live Patch Building (js2max) - Add generated objects to a patch that is already open in Max, and read an edited patch back into Python -- no save-and-reopen cycle
-
High Test Coverage - 700+ tests, plus 230+ for the js2max bridge
Max for Live (.amxd)
Generate Max for Live devices directly. Patcher.save() / Patcher.from_file() auto-detect the .amxd extension and read/write the binary device format, byte-for-byte compatible with Max-exported devices.
from py2max import Patcher
# device_type: "audio_effect" (default), "instrument", or "midi_effect"
p = Patcher('gain.amxd', device_type='audio_effect')
p.enable_presentation(devicewidth=120) # render Ableton's device strip
plugin = p.add_textbox('plugin~') # audio in from Live
gain = p.add('live.gain~', maxclass='live.gain~')
plugout = p.add_textbox('plugout~') # audio back to Live
gain.add_to_presentation([20, 20, 60, 136]) # show the fader in the device
p.add_line(plugin, gain, outlet=0, inlet=0)
p.add_line(gain, plugout, outlet=0, inlet=0)
p.save() # writes a binary .amxd
Helpers: Patcher.enable_presentation(devicewidth=...), Box.add_to_presentation([x, y, w, h]) (rejects M4L infrastructure objects and rounds fractional coordinates), and Patcher.enforce_integer_coords(). M4L binary helpers live in py2max.m4l.
Building patches inside Max (js2max)
py2max writes .maxpat files that Max later opens. js2max/ is the JavaScript counterpart, and does the one thing Python cannot: Max embeds a JavaScript engine, so a v8 script runs inside an open patcher and builds into it directly, from the same patch description py2max writes.
[import my-patch.json( [builddict my_patch(
| |
[dict my_patch] [v8 js2max.v8.js]
Both directions work and are confirmed against Max: a description becomes live objects, and a live patcher serializes back to a .maxpat that Max reopens.
The runtime ships with py2max, so pip install py2max is all you need:
p = Patcher('builder.maxpat')
p.add_v8_bridge() # adds [v8 js2max.v8.js]
p.save() # writes builder.maxpat AND js2max.v8.js beside it
Max resolves a bare filename through the folder holding the patch, so the two sitting together need no configuration. Nothing is written for a patch that never asked for the bridge. py2max.js2max_runtime.path() and install() are there if you would rather place it yourself.
Shipping them together is a correctness guarantee, not just a convenience: js2max/src/objects.ts -- the port counts for 1098 object classes -- is generated from py2max's own maxref data, so a runtime paired with a different py2max version would declare wrong ports, and a box declaring a port it does not have loses the cord attached to it when Max opens the file.
Full guide: Building Patches Inside Max. Source and what has been confirmed against Max: js2max/README.md. Building the runtime needs Bun (make js2max); using it does not.
Interactive Server (separate package)
Real-time browser-based patch editing with bidirectional sync lives in the companion py2max-server package, so the core library stays small and offline:
pip install py2max-server
py2max-server serve my-patch.maxpat
# Opens browser at http://localhost:8000
Features:
-
Drag objects, draw connections visually
-
Three layout engines: WebCola, ELK, and Dagre
-
Auto-save with debouncing
-
Navigate into subpatchers
-
REPL mode for Python interaction
SVG Preview
Generate high-quality SVG previews without Max:
py2max preview my-patch.maxpat --open
p = Patcher('synth.maxpat')
# ... add objects ...
p.to_svg('synth.svg', title="My Synth", show_ports=True)
Layout Managers
Five built-in layout strategies:
| Layout | Description |
|---|---|
grid |
Connection-aware clustering with configurable flow |
flow |
Signal flow-based hierarchical positioning |
columnar |
Controls -> Generators -> Processors -> Outputs |
matrix |
Signal chains in columns, categories in rows |
horizontal/vertical |
Simple grid layouts |
p = Patcher('patch.maxpat', layout='flow', flow_direction='vertical')
# Add objects and connections...
p.optimize_layout() # Arrange based on signal flow
p.save()
MaxRef Integration
Access documentation for 1175 Max objects:
p = Patcher('demo.maxpat')
cycle = p.add('cycle~ 440')
print(cycle.help()) # Full documentation
print(f"Inlets: {cycle.get_inlet_count()}")
print(f"Outlets: {cycle.get_outlet_count()}")
Connection Validation
Optional validation catches wiring errors:
p = Patcher('patch.maxpat', validate_connections=True)
osc = p.add('cycle~ 440')
gain = p.add('gain~')
p.link(osc, gain) # Valid
p.link(osc, gain, outlet=5) # Raises InvalidConnectionError
Semantic IDs
Human-readable object IDs for easier debugging:
p = Patcher('patch.maxpat', semantic_ids=True)
osc1 = p.add('cycle~ 440') # ID: 'cycle_1'
osc2 = p.add('cycle~ 220') # ID: 'cycle_2'
gain = p.add('gain~') # ID: 'gain_1'
# Find by semantic ID
osc = p.find_by_id('cycle_1')
SQLite Database
Query Max object metadata efficiently:
from py2max.maxref import MaxRefDB
db = MaxRefDB() # Auto-cached on first use
print(len(db)) # 1175 objects
if 'cycle~' in db:
info = db['cycle~']
print(info['digest'])
# Search and filter
results = db.search('filter')
msp_objects = db.by_category('MSP')
Usage Examples
Basic Patch Creation
from py2max import Patcher
p = Patcher('my-patch.maxpat')
osc = p.add('cycle~ 440')
gain = p.add('gain~')
dac = p.add('ezdac~')
p.link(osc, gain)
p.link(gain, dac)
p.link(gain, dac, inlet=1) # Stereo
p.save()
Loading and Modifying Patches
p = Patcher.from_file('existing.maxpat')
# Find and modify objects
for box in p.find_by_text('cycle~'):
print(f"Found oscillator: {box.id}")
p.save_as('modified.maxpat')
Subpatchers
p = Patcher('main.maxpat')
sbox = p.add_subpatcher('p mysub')
sp = sbox.subpatcher
# Build the subpatcher
inlet = sp.add('inlet')
gain = sp.add('gain~')
outlet = sp.add('outlet')
sp.link(inlet, gain)
sp.link(gain, outlet)
# Connect in main patcher
osc = p.add('cycle~ 440')
dac = p.add('ezdac~')
p.link(osc, sbox)
p.link(sbox, dac)
p.save()
Gen Codebox
add_gen_codebox() adds a standalone gen.codebox~ object -- a complete gen patch in a single box that sits directly in a regular Max patcher (unlike the inner codebox~ from add_codebox(), which belongs inside a gen~/rnbo~ subpatcher). Inlet/outlet counts are derived automatically from the highest inN/outN references in the code, and the box is sized to fit the code:
p = Patcher('fbdelay.maxpat')
# 1 inlet (in1), 1 outlet (out1)
osc = p.add('cycle~ 440')
cb = p.add_gen_codebox('''
Param feedback(0.5, min=0.0, max=0.95);
History fb(0.0);
out1 = in1 + fb * feedback;
fb = out1;
''')
dac = p.add('ezdac~')
p.link(osc, cb)
p.link(cb, dac)
p.save()
# Or via the add() string shortcut (single-line / `;`-terminated code)
cb = p.add('gen.codebox~ out1 = in1 * 0.5;')
Object Search
p = Patcher.from_file('complex-patch.maxpat')
# Find by ID
obj = p.find_by_id('obj-5')
# Find by text content
oscillators = p.find_by_text('cycle~')
# Find by object type
messages = p.find_by_type('message')
Command Line Interface
Patch Management
# Create new patch from template
py2max new demo.maxpat --template stereo
# Show patch info
py2max info demo.maxpat
# Generate SVG preview
py2max preview demo.maxpat --open
# Optimize layout
py2max optimize demo.maxpat --layout flow
# Validate connections
py2max validate demo.maxpat
Interactive Server
Provided by the separate py2max-server package (pip install py2max-server):
# Start server with browser editing
py2max-server serve my-patch.maxpat
# With REPL in same terminal
py2max-server serve my-patch.maxpat --repl
MaxRef Database
# Show cache status
py2max db cache location
# Create category-specific database
py2max db create msp.db --category msp
# Search objects
py2max db search maxref.db "oscillator" -v
# Query specific object
py2max db query maxref.db cycle~ --json
Converters
# Convert .maxpat to Python code
py2max convert maxpat-to-python patch.maxpat output.py
# Lookup object documentation
py2max maxref cycle~ --json
Use Cases
-
Scripted patch generation - Automate repetitive patch creation
-
Batch processing - Modify multiple
.maxpatfiles programmatically -
Parametric patches - Generate variations from configuration files
-
Test generation - Create
.maxhelpfiles during external development -
Container population - Prepopulate
coll,dict,tableobjects with data -
Generative patching - Algorithmic patch creation
-
CI/CD integration - SVG previews for documentation and version control
-
Live patch building - iterate on a patch while it stays open in Max, with js2max
Testing
make test # Run all tests
make typecheck # Type checking with mypy
make lint # Linting with ruff
make docs # Build documentation
make js2max-check # Typecheck and test js2max, and verify its artifacts are current (needs bun)
Design Notes
The .maxpat JSON format maps directly to three Python classes:
-
Patcher- The patch container with boxes and patchlines -
Box- Individual Max objects -
Patchline- Connections between boxes
All classes are extendable via **kwargs, allowing any Max object configuration. The add_textbox() method handles most objects, with specialized methods (add_subpatcher(), add_coll(), etc.) for objects requiring extra configuration.
Caveats
-
Max 9.2.0 and later reload an open patch, and its abstractions, when the file changes on disk, so
p.save()shows up in the open window. DSP and window state are kept. Object state (collcontents, number box values) is reset. Unsaved edits made in Max are discarded without a prompt, so save in Max, or read the patch back withPatcher.from_file(), before saving over it from Python. Max follows in-place writes only: a tool that saves by replacing the file (write a temp file, then rename) stops the reloads until the patch is reopened. Earlier versions do not reload: close and reopen to see changes, or build straight into the open patch with js2max, or usepy2max-server serve(from the separatepy2max-serverpackage) for live editing -
For tilde variants, use the
_tildesuffix:p.add_gen()vsp.add_gen_tilde() -
API docs in progress - see
CLAUDE.mdfor comprehensive usage
Examples
The tests/examples/ directory contains working, tested examples organized by topic (see its README):
-
quickstart/basic_patch.py- Simple oscillator patch -
tutorial/signal_processing_chain.py- Complex audio processing chain -
tutorial/generative_music.py- Generative music system with patterns -
layout/grid_layout_examples.py- Grid layout with clustering -
advanced/data_containers.py- Tables, collections, and dictionaries -
api/patcher_api_examples.py- Patcher API reference examples
External usage:
- faust2rnbo - Generate Max patchers for RNBO
Contributing
We welcome contributions! See CONTRIBUTING.md for guidelines.
git clone https://github.com/shakfu/py2max.git
cd py2max
make install
source .venv/bin/activate
make test # Verify setup
License
MIT License. See LICENSE for details.
Credits
-
HOLA algorithm: Kieffer, Dwyer, Marriott, Wybrow (IEEE 2016)
-
NetworkX: Hagberg, Schult, Swart (SciPy 2008)
-
Graph drawing techniques: Gansner, Koutsofios, North, Vo (IEEE 1993)
Metadata
Release files for py2max 0.5.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| py2max-0.5.1.tar.gz | 1.4 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| py2max-0.5.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 2.7 MB
Release files / py2max-0.5.1.tar.gz
| Download URL | py2max-0.5.1.tar.gz |
|---|---|
| Size | 1.4 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
559408cf74ef5fabb32023cc73c22d125eacf4c9036acdfe8d29cd6a926eea11
|
|
BLAKE2b-256 checksum How to use checksums |
a5c8f2fc325b0eda680dfe1b0444cf77a223570a6aad51e4b68c0340e253774b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.2
|
Release files / py2max-0.5.1-py3-none-any.whl
| Download URL | py2max-0.5.1-py3-none-any.whl |
|---|---|
| Size | 1.3 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2c8a48a5cba8502d28c532d9afc42b27b7c83b6325e9aaf4f25b44417da634b4
|
|
BLAKE2b-256 checksum How to use checksums |
f407e547c17b2cd5fad8a6c25fcffaceec5fe512a7988fe9cb601e85f3b88427
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.2
|