Metadata-Version: 2.4
Name: py2max
Version: 0.3.4
Summary: A library for offline generation of Max/MSP patcher (.maxpat) files.
Keywords: Max,maxpat,offline
Author: Shakeeb Alireza
Author-email: Shakeeb Alireza <shakfu@users.noreply.github.com>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: End Users/Desktop
Classifier: Topic :: Multimedia :: Sound/Audio
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Operating System :: OS Independent
Classifier: Typing :: Typed
Requires-Dist: graph-layout>=0.4 ; extra == 'graph'
Requires-Dist: ogdf-py>=0.3 ; python_full_version >= '3.10' and extra == 'graph'
Requires-Dist: hola-graph>=0.1 ; extra == 'graph'
Maintainer: Shakeeb Alireza
Maintainer-email: Shakeeb Alireza <shakfu@users.noreply.github.com>
Requires-Python: >=3.9
Project-URL: Homepage, https://github.com/shakfu/py2max
Project-URL: Documentation, https://github.com/shakfu/py2max#readme
Project-URL: Repository, https://github.com/shakfu/py2max.git
Project-URL: Issues, https://github.com/shakfu/py2max/issues
Project-URL: Changelog, https://github.com/shakfu/py2max/blob/main/CHANGELOG.md
Provides-Extra: graph
Description-Content-Type: text/markdown

# py2max

[![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

A pure Python library for offline generation of Max/MSP patcher files (`.maxpat`, `.maxhelp`, `.rbnopat`).

If you are looking for Python 3 externals for Max/MSP, check out the [py-js](https://github.com/shakfu/py-js) project.

## Installation

```bash
pip install py2max
```

For the browser-based live editor and remote REPL, install the companion
[`py2max-server`](https://github.com/shakfu/py2max-server) package:

```bash
pip install py2max-server
```

For development:

```bash
git clone https://github.com/shakfu/py2max.git
cd py2max
uv sync
source .venv/bin/activate
```

## Quick Start

```python
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 `.maxpat` files
- **Max for Live (.amxd)** - Read/write binary `.amxd` device files with presentation-mode helpers
- **Universal Object Support** - Works with any Max/MSP/Jitter object
- **Fully typed** - Passes `mypy --strict`; no runtime dependencies
- **High Test Coverage** - 420+ tests ensure reliability

### 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.

```python
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`.

### Interactive Server (separate package)

Real-time browser-based patch editing with bidirectional sync lives in the
companion [`py2max-server`](https://github.com/shakfu/py2max-server) package, so
the core library stays small and offline:

```bash
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:

```bash
py2max preview my-patch.maxpat --open
```

```python
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 |

```python
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:

```python
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:

```python
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:

```python
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:

```python
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

```python
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

```python
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

```python
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:

```python
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

```python
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

```bash
# 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`](https://github.com/shakfu/py2max-server)
package (`pip install py2max-server`):

```bash
# 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

```bash
# 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

```bash
# 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 `.maxpat` files programmatically
- **Parametric patches** - Generate variations from configuration files
- **Test generation** - Create `.maxhelp` files during external development
- **Container population** - Prepopulate `coll`, `dict`, `table` objects with data
- **Generative patching** - Algorithmic patch creation
- **CI/CD integration** - SVG previews for documentation and version control

## Testing

```bash
make test        # Run all tests
make typecheck   # Type checking with mypy
make lint        # Linting with ruff
make docs        # Build documentation
```

## 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 doesn't refresh from file when open - close and reopen to see changes, or use `py2max-server serve` (from the separate `py2max-server` package) for live editing
- For tilde variants, use the `_tilde` suffix: `p.add_gen()` vs `p.add_gen_tilde()`
- API docs in progress - see `CLAUDE.md` for comprehensive usage

## Examples

The [`tests/examples/`](tests/examples/) directory contains working, tested
examples organized by topic (see its [README](tests/examples/README.md)):

- `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](https://github.com/grame-cncm/faust/blob/master-dev/architecture/max-msp/rnbo.py) - Generate Max patchers for RNBO

## Contributing

We welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.

```bash
git clone https://github.com/shakfu/py2max.git
cd py2max
uv sync
source .venv/bin/activate
make test  # Verify setup
```

## License

MIT License. See [LICENSE](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)
