py2max 0.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. py2max/__init__.py +67 -0
  2. py2max/__main__.py +6 -0
  3. py2max/cli.py +1251 -0
  4. py2max/core/__init__.py +39 -0
  5. py2max/core/abstract.py +146 -0
  6. py2max/core/box.py +231 -0
  7. py2max/core/common.py +19 -0
  8. py2max/core/patcher.py +1658 -0
  9. py2max/core/patchline.py +68 -0
  10. py2max/exceptions.py +385 -0
  11. py2max/export/__init__.py +20 -0
  12. py2max/export/converters.py +345 -0
  13. py2max/export/svg.py +393 -0
  14. py2max/layout/__init__.py +26 -0
  15. py2max/layout/base.py +463 -0
  16. py2max/layout/flow.py +405 -0
  17. py2max/layout/grid.py +374 -0
  18. py2max/layout/matrix.py +628 -0
  19. py2max/log.py +338 -0
  20. py2max/maxref/__init__.py +78 -0
  21. py2max/maxref/category.py +163 -0
  22. py2max/maxref/db.py +1082 -0
  23. py2max/maxref/legacy.py +324 -0
  24. py2max/maxref/parser.py +703 -0
  25. py2max/py.typed +0 -0
  26. py2max/server/__init__.py +54 -0
  27. py2max/server/client.py +295 -0
  28. py2max/server/inline.py +312 -0
  29. py2max/server/repl.py +561 -0
  30. py2max/server/rpc.py +240 -0
  31. py2max/server/websocket.py +997 -0
  32. py2max/static/cola.min.js +4 -0
  33. py2max/static/d3.v7.min.js +2 -0
  34. py2max/static/dagre-bundle.js +328 -0
  35. py2max/static/elk.bundled.js +6663 -0
  36. py2max/static/index.html +168 -0
  37. py2max/static/interactive.html +589 -0
  38. py2max/static/interactive.js +2111 -0
  39. py2max/static/live-preview.js +324 -0
  40. py2max/static/svg.min.js +13 -0
  41. py2max/static/svg.min.js.map +1 -0
  42. py2max/transformers.py +168 -0
  43. py2max/utils.py +83 -0
  44. py2max-0.2.1.dist-info/METADATA +390 -0
  45. py2max-0.2.1.dist-info/RECORD +48 -0
  46. py2max-0.2.1.dist-info/WHEEL +4 -0
  47. py2max-0.2.1.dist-info/entry_points.txt +3 -0
  48. py2max-0.2.1.dist-info/licenses/LICENSE +19 -0
py2max/transformers.py ADDED
@@ -0,0 +1,168 @@
1
+ """Composable patcher transformers for py2max."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Callable, Dict, Iterable, NamedTuple, Optional, Sequence
6
+
7
+ from .core import Patcher
8
+
9
+ Transformer = Callable[[Patcher], Patcher]
10
+ TransformerFactory = Callable[[Optional[str]], Transformer]
11
+
12
+
13
+ class TransformerSpec(NamedTuple):
14
+ factory: TransformerFactory
15
+ description: str
16
+
17
+
18
+ def compose(transformers: Sequence[Transformer]) -> Transformer:
19
+ """Compose multiple transformers into a single operation."""
20
+
21
+ def _composed(patcher: Patcher) -> Patcher:
22
+ current = patcher
23
+ for transform in transformers:
24
+ current = transform(current)
25
+ return current
26
+
27
+ return _composed
28
+
29
+
30
+ def run_pipeline(patcher: Patcher, transformers: Iterable[Transformer]) -> Patcher:
31
+ """Apply each transformer in order to the patcher."""
32
+
33
+ current = patcher
34
+ for transform in transformers:
35
+ current = transform(current)
36
+ return current
37
+
38
+
39
+ def optimize_layout(layout: str | None = None) -> Transformer:
40
+ """Return a transformer that optimizes layout with an optional override."""
41
+
42
+ def _transform(patcher: Patcher) -> Patcher:
43
+ if layout:
44
+ patcher._layout_mgr = patcher.set_layout_mgr(layout)
45
+ patcher.optimize_layout()
46
+ return patcher
47
+
48
+ return _transform
49
+
50
+
51
+ def set_flow_direction(direction: str) -> Transformer:
52
+ """Set the flow direction on the current layout manager."""
53
+
54
+ def _transform(patcher: Patcher) -> Patcher:
55
+ patcher._flow_direction = direction
56
+ if hasattr(patcher._layout_mgr, "flow_direction"):
57
+ patcher._layout_mgr.flow_direction = direction
58
+ return patcher
59
+
60
+ return _transform
61
+
62
+
63
+ def add_comment_transform(comment: str, pos: str = "above") -> Transformer:
64
+ """Attach a comment near every object in the patcher."""
65
+
66
+ def _transform(patcher: Patcher) -> Patcher:
67
+ for box in list(patcher._boxes):
68
+ if box.id:
69
+ patcher.add_associated_comment(box, comment, comment_pos=pos) # type: ignore[arg-type]
70
+ patcher._process_pending_comments()
71
+ return patcher
72
+
73
+ return _transform
74
+
75
+
76
+ def set_font_size(size: float) -> Transformer:
77
+ """Set default font size for the patcher and all boxes."""
78
+
79
+ def _transform(patcher: Patcher) -> Patcher:
80
+ patcher.default_fontsize = size
81
+ for box in patcher._boxes:
82
+ box._kwds["fontsize"] = size
83
+ return patcher
84
+
85
+ return _transform
86
+
87
+
88
+ def _set_font_size_factory(arg: Optional[str]) -> Transformer:
89
+ value = float(arg) if arg is not None else 12.0
90
+ return set_font_size(value)
91
+
92
+
93
+ def _add_comment_factory(arg: Optional[str]) -> Transformer:
94
+ text = arg or "Auto"
95
+ return add_comment_transform(text)
96
+
97
+
98
+ def _optimize_layout_factory(arg: Optional[str]) -> Transformer:
99
+ return optimize_layout(layout=arg)
100
+
101
+
102
+ def _set_flow_direction_factory(arg: Optional[str]) -> Transformer:
103
+ direction = arg or "horizontal"
104
+ return set_flow_direction(direction)
105
+
106
+
107
+ _TRANSFORMERS: Dict[str, TransformerSpec] = {
108
+ "set-font-size": TransformerSpec(
109
+ _set_font_size_factory, "Set default font size (points). Default 12.0"
110
+ ),
111
+ "add-comment": TransformerSpec(
112
+ _add_comment_factory, "Attach the supplied comment text above each box"
113
+ ),
114
+ "optimize-layout": TransformerSpec(
115
+ _optimize_layout_factory,
116
+ "Run layout optimisation (optionally specify layout name)",
117
+ ),
118
+ "set-flow-direction": TransformerSpec(
119
+ _set_flow_direction_factory,
120
+ "Adjust flow direction for the active layout manager (horizontal/vertical/column)",
121
+ ),
122
+ }
123
+
124
+
125
+ def available_transformers() -> Dict[str, str]:
126
+ """Return available transformer names mapped to short descriptions."""
127
+
128
+ return {name: spec.description for name, spec in _TRANSFORMERS.items()}
129
+
130
+
131
+ def create_transformer(name: str, argument: Optional[str] = None) -> Transformer:
132
+ """Instantiate a transformer by name.
133
+
134
+ Args:
135
+ name: Registered transformer name.
136
+ argument: Optional string argument passed to the transformer factory.
137
+
138
+ Raises:
139
+ KeyError: If the transformer name is unknown.
140
+ ValueError: If the argument cannot be parsed.
141
+ """
142
+
143
+ try:
144
+ spec = _TRANSFORMERS[name]
145
+ except KeyError as exc: # pragma: no cover - defensive
146
+ raise KeyError(f"Unknown transformer '{name}'") from exc
147
+
148
+ try:
149
+ return spec.factory(argument)
150
+ except ValueError as exc: # pragma: no cover - argument parsing failure
151
+ raise ValueError(
152
+ f"Invalid argument for transformer '{name}': {argument}"
153
+ ) from exc
154
+
155
+
156
+ __all__ = [
157
+ "Transformer",
158
+ "TransformerFactory",
159
+ "TransformerSpec",
160
+ "compose",
161
+ "run_pipeline",
162
+ "optimize_layout",
163
+ "set_flow_direction",
164
+ "add_comment_transform",
165
+ "set_font_size",
166
+ "available_transformers",
167
+ "create_transformer",
168
+ ]
py2max/utils.py ADDED
@@ -0,0 +1,83 @@
1
+ """Utility functions for py2max.
2
+
3
+ This module provides helper functions for common Max/MSP operations
4
+ such as pitch-to-frequency conversion and other musical calculations.
5
+ """
6
+
7
+ NOTE_TO_SEMITONE = {
8
+ "C": 0,
9
+ "C#": 1,
10
+ "Db": 1,
11
+ "D": 2,
12
+ "D#": 3,
13
+ "Eb": 3,
14
+ "E": 4,
15
+ "Fb": 4,
16
+ "E#": 5,
17
+ "F": 5,
18
+ "F#": 6,
19
+ "Gb": 6,
20
+ "G": 7,
21
+ "G#": 8,
22
+ "Ab": 8,
23
+ "A": 9,
24
+ "A#": 10,
25
+ "Bb": 10,
26
+ "B": 11,
27
+ "Cb": 11,
28
+ "B#": 0,
29
+ }
30
+
31
+
32
+ def pitch2freq(pitch, A4=440):
33
+ """Convert a pitch name to a frequency in Hz.
34
+
35
+ Converts standard pitch notation (e.g., "C4", "A#3") to frequency
36
+ values using equal temperament tuning.
37
+
38
+ Args:
39
+ pitch: Pitch name in format like "C4", "A#3", "Bb2".
40
+ A4: Reference frequency for A4 (default: 440 Hz).
41
+
42
+ Returns:
43
+ Frequency in Hz as a float.
44
+
45
+ Example:
46
+ >>> pitch2freq("C3")
47
+ 130.8127826502993
48
+ >>> pitch2freq("A4")
49
+ 440.0
50
+
51
+ Note:
52
+ Based on: https://gist.github.com/CGrassin/26a1fdf4fc5de788da9b376ff717516e
53
+ """
54
+
55
+ token = pitch.strip()
56
+ if len(token) < 2:
57
+ raise ValueError(f"Invalid pitch name: '{pitch}'")
58
+
59
+ note = token[0].upper()
60
+ idx = 1
61
+
62
+ if idx < len(token) and token[idx] in ("#", "b", "♯", "♭"):
63
+ accidental = token[idx]
64
+ if accidental == "♯":
65
+ accidental = "#"
66
+ elif accidental == "♭":
67
+ accidental = "b"
68
+ note += accidental
69
+ idx += 1
70
+
71
+ octave_part = token[idx:]
72
+ try:
73
+ octave = int(octave_part)
74
+ except ValueError as exc:
75
+ raise ValueError(f"Invalid octave in pitch name: '{pitch}'") from exc
76
+
77
+ if note not in NOTE_TO_SEMITONE:
78
+ raise ValueError(f"Unsupported note name: '{pitch}'")
79
+
80
+ semitone = NOTE_TO_SEMITONE[note]
81
+ midi_number = semitone + (octave + 1) * 12
82
+ distance_from_A4 = midi_number - 69
83
+ return float(A4) * 2 ** (distance_from_A4 / 12)
@@ -0,0 +1,390 @@
1
+ Metadata-Version: 2.4
2
+ Name: py2max
3
+ Version: 0.2.1
4
+ Summary: A library for offline generation of Max/MSP patcher (.maxpat) files.
5
+ Keywords: Max,maxpat,offline
6
+ Author: Shakeeb Alireza
7
+ Author-email: Shakeeb Alireza <shakfu@users.noreply.github.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: End Users/Desktop
13
+ Classifier: Topic :: Multimedia :: Sound/Audio
14
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Operating System :: OS Independent
24
+ Classifier: Typing :: Typed
25
+ Requires-Dist: websockets>=12.0 ; extra == 'server'
26
+ Requires-Dist: ptpython>=3.0.0 ; extra == 'server'
27
+ Requires-Python: >=3.9
28
+ Project-URL: Changelog, https://github.com/shakfu/py2max/blob/main/CHANGELOG.md
29
+ Project-URL: Documentation, https://github.com/shakfu/py2max#readme
30
+ Project-URL: Homepage, https://github.com/shakfu/py2max
31
+ Project-URL: Issues, https://github.com/shakfu/py2max/issues
32
+ Project-URL: Repository, https://github.com/shakfu/py2max.git
33
+ Provides-Extra: server
34
+ Description-Content-Type: text/markdown
35
+
36
+ # py2max
37
+
38
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
39
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
40
+
41
+ A pure Python library for offline generation of Max/MSP patcher files (`.maxpat`, `.maxhelp`, `.rbnopat`).
42
+
43
+ If you are looking for Python 3 externals for Max/MSP, check out the [py-js](https://github.com/shakfu/py-js) project.
44
+
45
+ ## Installation
46
+
47
+ ```bash
48
+ pip install py2max
49
+
50
+ # With interactive server support
51
+ pip install py2max[server]
52
+ ```
53
+
54
+ For development:
55
+
56
+ ```bash
57
+ git clone https://github.com/shakfu/py2max.git
58
+ cd py2max
59
+ uv sync
60
+ source .venv/bin/activate
61
+ ```
62
+
63
+ ## Quick Start
64
+
65
+ ```python
66
+ from py2max import Patcher
67
+
68
+ p = Patcher('my-synth.maxpat')
69
+ osc = p.add('cycle~ 440')
70
+ gain = p.add('gain~')
71
+ dac = p.add('ezdac~')
72
+ p.link(osc, gain)
73
+ p.link(gain, dac)
74
+ p.save()
75
+ ```
76
+
77
+ That's it! Open `my-synth.maxpat` in Max to see your patch.
78
+
79
+ ## Features
80
+
81
+ ### Core Capabilities
82
+
83
+ - **Offline Patch Generation** - Create Max patches programmatically without Max running
84
+ - **Round-trip Conversion** - Load, modify, and save existing `.maxpat` files
85
+ - **Universal Object Support** - Works with any Max/MSP/Jitter object
86
+ - **99% Test Coverage** - 418+ tests ensure reliability
87
+
88
+ ### Interactive Server (New in 0.2.x)
89
+
90
+ Real-time browser-based patch editing with bidirectional sync:
91
+
92
+ ```bash
93
+ py2max serve my-patch.maxpat
94
+ # Opens browser at http://localhost:8000
95
+ ```
96
+
97
+ **Features:**
98
+
99
+ - Drag objects, draw connections visually
100
+ - Three layout engines: **WebCola**, **ELK**, and **Dagre**
101
+ - Auto-save with debouncing
102
+ - Navigate into subpatchers
103
+ - REPL mode for Python interaction
104
+
105
+ ### SVG Preview
106
+
107
+ Generate high-quality SVG previews without Max:
108
+
109
+ ```bash
110
+ py2max preview my-patch.maxpat --open
111
+ ```
112
+
113
+ ```python
114
+ p = Patcher('synth.maxpat')
115
+ # ... add objects ...
116
+ p.to_svg('synth.svg', title="My Synth", show_ports=True)
117
+ ```
118
+
119
+ ### Layout Managers
120
+
121
+ Five built-in layout strategies:
122
+
123
+ | Layout | Description |
124
+ |--------|-------------|
125
+ | `grid` | Connection-aware clustering with configurable flow |
126
+ | `flow` | Signal flow-based hierarchical positioning |
127
+ | `columnar` | Controls -> Generators -> Processors -> Outputs |
128
+ | `matrix` | Signal chains in columns, categories in rows |
129
+ | `horizontal`/`vertical` | Simple grid layouts |
130
+
131
+ ```python
132
+ p = Patcher('patch.maxpat', layout='flow', flow_direction='vertical')
133
+ # Add objects and connections...
134
+ p.optimize_layout() # Arrange based on signal flow
135
+ p.save()
136
+ ```
137
+
138
+ ### MaxRef Integration
139
+
140
+ Access documentation for 1157 Max objects:
141
+
142
+ ```python
143
+ p = Patcher('demo.maxpat')
144
+ cycle = p.add('cycle~ 440')
145
+
146
+ print(cycle.help()) # Full documentation
147
+ print(f"Inlets: {cycle.get_inlet_count()}")
148
+ print(f"Outlets: {cycle.get_outlet_count()}")
149
+ ```
150
+
151
+ ### Connection Validation
152
+
153
+ Optional validation catches wiring errors:
154
+
155
+ ```python
156
+ p = Patcher('patch.maxpat', validate_connections=True)
157
+ osc = p.add('cycle~ 440')
158
+ gain = p.add('gain~')
159
+
160
+ p.link(osc, gain) # Valid
161
+ p.link(osc, gain, outlet=5) # Raises InvalidConnectionError
162
+ ```
163
+
164
+ ### Semantic IDs
165
+
166
+ Human-readable object IDs for easier debugging:
167
+
168
+ ```python
169
+ p = Patcher('patch.maxpat', semantic_ids=True)
170
+
171
+ osc1 = p.add('cycle~ 440') # ID: 'cycle_1'
172
+ osc2 = p.add('cycle~ 220') # ID: 'cycle_2'
173
+ gain = p.add('gain~') # ID: 'gain_1'
174
+
175
+ # Find by semantic ID
176
+ osc = p.find_by_id('cycle_1')
177
+ ```
178
+
179
+ ### SQLite Database
180
+
181
+ Query Max object metadata efficiently:
182
+
183
+ ```python
184
+ from py2max import MaxRefDB
185
+
186
+ db = MaxRefDB() # Auto-cached on first use
187
+ print(len(db)) # 1157 objects
188
+
189
+ if 'cycle~' in db:
190
+ info = db['cycle~']
191
+ print(info['digest'])
192
+
193
+ # Search and filter
194
+ results = db.search('filter')
195
+ msp_objects = db.by_category('MSP')
196
+ ```
197
+
198
+ ## Usage Examples
199
+
200
+ ### Basic Patch Creation
201
+
202
+ ```python
203
+ from py2max import Patcher
204
+
205
+ p = Patcher('my-patch.maxpat')
206
+ osc = p.add('cycle~ 440')
207
+ gain = p.add('gain~')
208
+ dac = p.add('ezdac~')
209
+
210
+ p.link(osc, gain)
211
+ p.link(gain, dac)
212
+ p.link(gain, dac, inlet=1) # Stereo
213
+ p.save()
214
+ ```
215
+
216
+ ### Loading and Modifying Patches
217
+
218
+ ```python
219
+ p = Patcher.from_file('existing.maxpat')
220
+
221
+ # Find and modify objects
222
+ for box in p.find_by_text('cycle~'):
223
+ print(f"Found oscillator: {box.id}")
224
+
225
+ p.save_as('modified.maxpat')
226
+ ```
227
+
228
+ ### Subpatchers
229
+
230
+ ```python
231
+ p = Patcher('main.maxpat')
232
+ sbox = p.add_subpatcher('p mysub')
233
+ sp = sbox.subpatcher
234
+
235
+ # Build the subpatcher
236
+ inlet = sp.add('inlet')
237
+ gain = sp.add('gain~')
238
+ outlet = sp.add('outlet')
239
+ sp.link(inlet, gain)
240
+ sp.link(gain, outlet)
241
+
242
+ # Connect in main patcher
243
+ osc = p.add('cycle~ 440')
244
+ dac = p.add('ezdac~')
245
+ p.link(osc, sbox)
246
+ p.link(sbox, dac)
247
+ p.save()
248
+ ```
249
+
250
+ ### Object Search
251
+
252
+ ```python
253
+ p = Patcher.from_file('complex-patch.maxpat')
254
+
255
+ # Find by ID
256
+ obj = p.find_by_id('obj-5')
257
+
258
+ # Find by text content
259
+ oscillators = p.find_by_text('cycle~')
260
+
261
+ # Find by object type
262
+ messages = p.find_by_type('message')
263
+ ```
264
+
265
+ ## Command Line Interface
266
+
267
+ ### Patch Management
268
+
269
+ ```bash
270
+ # Create new patch from template
271
+ py2max new demo.maxpat --template stereo
272
+
273
+ # Show patch info
274
+ py2max info demo.maxpat
275
+
276
+ # Generate SVG preview
277
+ py2max preview demo.maxpat --open
278
+
279
+ # Optimize layout
280
+ py2max optimize demo.maxpat --layout flow
281
+
282
+ # Validate connections
283
+ py2max validate demo.maxpat
284
+ ```
285
+
286
+ ### Interactive Server
287
+
288
+ ```bash
289
+ # Start server with browser editing
290
+ py2max serve my-patch.maxpat
291
+
292
+ # With REPL in same terminal
293
+ py2max serve my-patch.maxpat --repl
294
+ ```
295
+
296
+ ### MaxRef Database
297
+
298
+ ```bash
299
+ # Show cache status
300
+ py2max db cache location
301
+
302
+ # Create category-specific database
303
+ py2max db create msp.db --category msp
304
+
305
+ # Search objects
306
+ py2max db search maxref.db "oscillator" -v
307
+
308
+ # Query specific object
309
+ py2max db query maxref.db cycle~ --json
310
+ ```
311
+
312
+ ### Converters
313
+
314
+ ```bash
315
+ # Convert .maxpat to Python code
316
+ py2max convert maxpat-to-python patch.maxpat output.py
317
+
318
+ # Lookup object documentation
319
+ py2max maxref cycle~ --json
320
+ ```
321
+
322
+ ## Use Cases
323
+
324
+ - **Scripted patch generation** - Automate repetitive patch creation
325
+ - **Batch processing** - Modify multiple `.maxpat` files programmatically
326
+ - **Parametric patches** - Generate variations from configuration files
327
+ - **Test generation** - Create `.maxhelp` files during external development
328
+ - **Container population** - Prepopulate `coll`, `dict`, `table` objects with data
329
+ - **Generative patching** - Algorithmic patch creation
330
+ - **CI/CD integration** - SVG previews for documentation and version control
331
+
332
+ ## Testing
333
+
334
+ ```bash
335
+ make test # Run all tests
336
+ make typecheck # Type checking with mypy
337
+ make lint # Linting with ruff
338
+ make docs # Build documentation
339
+ ```
340
+
341
+ ## Design Notes
342
+
343
+ The `.maxpat` JSON format maps directly to three Python classes:
344
+
345
+ - **`Patcher`** - The patch container with boxes and patchlines
346
+ - **`Box`** - Individual Max objects
347
+ - **`Patchline`** - Connections between boxes
348
+
349
+ 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.
350
+
351
+ ## Caveats
352
+
353
+ - Max doesn't refresh from file when open - close and reopen to see changes, or use `py2max serve` for live editing
354
+ - For tilde variants, use the `_tilde` suffix: `p.add_gen()` vs `p.add_gen_tilde()`
355
+ - API docs in progress - see `CLAUDE.md` for comprehensive usage
356
+
357
+ ## Examples
358
+
359
+ See the `examples/` directory for demonstrations:
360
+
361
+ - `auto_layout_demo.py` - Complex synthesizer with layout optimization
362
+ - `nested_patcher_demo.py` - Subpatcher navigation
363
+ - `columnar_layout_demo.py` - Functional column organization
364
+ - `matrix_layout_demo.py` - Signal chain matrix layout
365
+
366
+ External usage:
367
+
368
+ - [faust2rnbo](https://github.com/grame-cncm/faust/blob/master-dev/architecture/max-msp/rnbo.py) - Generate Max patchers for RNBO
369
+
370
+ ## Contributing
371
+
372
+ We welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
373
+
374
+ ```bash
375
+ git clone https://github.com/shakfu/py2max.git
376
+ cd py2max
377
+ uv sync
378
+ source .venv/bin/activate
379
+ make test # Verify setup
380
+ ```
381
+
382
+ ## License
383
+
384
+ MIT License. See [LICENSE](LICENSE) for details.
385
+
386
+ ## Credits
387
+
388
+ - HOLA algorithm: Kieffer, Dwyer, Marriott, Wybrow (IEEE 2016)
389
+ - NetworkX: Hagberg, Schult, Swart (SciPy 2008)
390
+ - Graph drawing techniques: Gansner, Koutsofios, North, Vo (IEEE 1993)
@@ -0,0 +1,48 @@
1
+ py2max/__init__.py,sha256=CvkhiHI_fmkjcTNXSYbkdPVxBV3QxL9TUEvlrZo0fJo,1994
2
+ py2max/__main__.py,sha256=dO6lxrMrGVDBd8DWi9tuD5s1XPMrcEPL50VSX-UT3u4,119
3
+ py2max/cli.py,sha256=2yDj3r_l98ZQn8B19ifIy5rVkhEYRYs6Wnh0_BjPA5E,40217
4
+ py2max/core/__init__.py,sha256=fsQW2xtkdQMpQjd9Ki_gFlKEtOZKL6diKil8hECaD4Y,995
5
+ py2max/core/abstract.py,sha256=y9p-Q0Sridsq2_K0fX4-x5gQvLHKLolsfGCkMwnfhCs,3848
6
+ py2max/core/box.py,sha256=ap4gfbIW6NlWwyy-IBxa7lLQi3Pq2tgGWmUWSf4fYCg,7336
7
+ py2max/core/common.py,sha256=cKNf0YiPMbmchBdJMNZ9oTS9duBIP8R-a3go5E__JIE,474
8
+ py2max/core/patcher.py,sha256=Ff_6GgSlQpHTmNXYzT510lvR-wKPz9SgKwh5t2JhW-k,57922
9
+ py2max/core/patchline.py,sha256=CABM1AljmMHqJXd0Iitq94MPg17f_eixRko_T1XMA4U,2056
10
+ py2max/exceptions.py,sha256=t0D_H-y9uqYYHPgPIA10V0elrkfELkvIvTkF5GbYnp0,10886
11
+ py2max/export/__init__.py,sha256=m7aiFGN-iVLLcn60Bx2WUzqUC1V0joxa3eNwlBEHpFA,502
12
+ py2max/export/converters.py,sha256=0MvqcJJrCMsGvzdBo_xQBNuXir_WnSxcuc0fVzs9IuA,10333
13
+ py2max/export/svg.py,sha256=XKF4ZSXZVVoshSIMbii3Ue96cjl_z8CP28kTYHao0DM,11592
14
+ py2max/layout/__init__.py,sha256=cG2dOm5NCW7FA65I7F0f_r2XKvpiF6yc4gIylxd13h0,893
15
+ py2max/layout/base.py,sha256=87SCXFHeJiw5UIRX5cHGDAZnrXo9D-tCliNXHV4uiyA,16571
16
+ py2max/layout/flow.py,sha256=xYhybUmk7gB74tl6DvU3yi3unitPzWLhEiUuHkV_IJA,15764
17
+ py2max/layout/grid.py,sha256=k4JnjJ9wqLdSBFT4YTNaHUX1DMrD-BeH7K7Fwa3U-PE,15054
18
+ py2max/layout/matrix.py,sha256=S8HnOjLpGDFsW86PZhYCxE2DjRTQPIiQzzVsmid3U_k,23966
19
+ py2max/log.py,sha256=pOChtQaydUyhPKvcxRxmoTCz24bSI7v3PhOWvX22Ofo,10118
20
+ py2max/maxref/__init__.py,sha256=FJy0iSBttIHtP_QvaT1zOtGuxJf6S274nU8P57UH5KI,1916
21
+ py2max/maxref/category.py,sha256=gxJAdk1Ruv66iEqpZRF7nN358ZuMJBQG5sHKtICfVz4,2698
22
+ py2max/maxref/db.py,sha256=KvGPNUzTJG_B_NtQ2vw3nLYo3gNaPVsxWYh1Ee0_Lk0,39365
23
+ py2max/maxref/legacy.py,sha256=uaG1kwkqIl8EuMNhfrs4fQxh8BI2g6IJGbOKMlaX1wM,9571
24
+ py2max/maxref/parser.py,sha256=Su95KXCPiSzrSioWdcEEx47Bi05I2TeuQtmEvWjNxLM,26913
25
+ py2max/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
+ py2max/server/__init__.py,sha256=RMx5_Yqp_PXPKm2EkEh4mH4eRMBnUWqj61oD8sJ4khk,1415
27
+ py2max/server/client.py,sha256=Io1ywNjXl63NJfMyTTh9S9Q_0nPq_dmjebuaPv7MdKw,9601
28
+ py2max/server/inline.py,sha256=bRFmg9Dm-dqfXRhgdQo3D4lIB2TLVi8FzwF9i702kXw,9894
29
+ py2max/server/repl.py,sha256=oLyAlZiMkg0VKiSMCBAlzxl53Rn1S5c9pJqIUws7Kuw,17483
30
+ py2max/server/rpc.py,sha256=nuYblQ2KXN7prlWLEbPchsBgrIFUoKNOyZujPgeTZaw,7362
31
+ py2max/server/websocket.py,sha256=E7GgOP78klpJEjz7dIcGEWCnPPSujgxY_U5gQv9ftvU,34584
32
+ py2max/static/cola.min.js,sha256=mDG9_3AMjpWdMq0_FA4VpiZohZiseIMlzfhEFLobSS0,79920
33
+ py2max/static/d3.v7.min.js,sha256=8glLv2FBs1lyLE_kVOtsSw8OQswQzHr5IfwVj864ZTk,279706
34
+ py2max/static/dagre-bundle.js,sha256=nthMZbMQ8QAocYVWEj7LBqX1jcxGFMguf5rItidDEss,60075
35
+ py2max/static/elk.bundled.js,sha256=y_YbAYLpCF023NWzkvV8yBYnMWmsQL3oC1K4CERMXPg,1627496
36
+ py2max/static/index.html,sha256=Xy30GBVJCEzKWttg1O8YPlpkN5kwXLeEakNQnfneEcI,4221
37
+ py2max/static/interactive.html,sha256=XCrSQ4JHe7k8kxwek7niTDwZ7sOPIdOwUelba9s0c0Y,20841
38
+ py2max/static/interactive.js,sha256=Uv5Gs_QJslYQwJRvMe9D0AD59RRSfpAjZnjwRe2bwL8,79071
39
+ py2max/static/live-preview.js,sha256=rnEDaphwP9qz1gS9p-Pq8zdWc3Yv5ulcn3mRJfKq174,10325
40
+ py2max/static/svg.min.js,sha256=hGPJhrshVc5fAwOf74KsM4FmRgVFNGgfJoBjroxUa_I,79567
41
+ py2max/static/svg.min.js.map,sha256=AZ9-hHUj_yRBXB9Ie6T0YqmPsbbdkGT2Cw5zFEpUcPs,330608
42
+ py2max/transformers.py,sha256=CRBeAKSSx0VyQnkTX9qHmd3XBprxrQhWykrPCQHwh6Q,4909
43
+ py2max/utils.py,sha256=zBxw1pGK89po3QK-TOcf6diuLqpVtC3FZchzyfY7WaQ,1941
44
+ py2max-0.2.1.dist-info/licenses/LICENSE,sha256=tBlNvY4s8JHbskvSQJnSBQWSPXdvEJv7rwUxDE7d0iA,1059
45
+ py2max-0.2.1.dist-info/WHEEL,sha256=eycQt0QpYmJMLKpE3X9iDk8R04v2ZF0x82ogq-zP6bQ,79
46
+ py2max-0.2.1.dist-info/entry_points.txt,sha256=uQFA1FvLH7eCa2S5bQYGdESNy6X8vbuc_cEal3ZlwbY,49
47
+ py2max-0.2.1.dist-info/METADATA,sha256=Hw96EV2vsipCzUBOWajlxZDNKgxZNa1J9yNxxV-E2Xw,9734
48
+ py2max-0.2.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.9.24
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ py2max = py2max.__main__:main
3
+