py2max 0.2.1__tar.gz → 0.3.1__tar.gz

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 (204) hide show
  1. {py2max-0.2.1 → py2max-0.3.1}/CHANGELOG.md +101 -0
  2. {py2max-0.2.1 → py2max-0.3.1}/PKG-INFO +91 -22
  3. {py2max-0.2.1 → py2max-0.3.1}/README.md +85 -14
  4. {py2max-0.2.1 → py2max-0.3.1}/py2max/__init__.py +5 -4
  5. {py2max-0.2.1 → py2max-0.3.1}/py2max/cli.py +38 -158
  6. {py2max-0.2.1 → py2max-0.3.1}/py2max/core/abstract.py +33 -5
  7. {py2max-0.2.1 → py2max-0.3.1}/py2max/core/box.py +80 -29
  8. py2max-0.3.1/py2max/core/colors.py +83 -0
  9. py2max-0.2.1/py2max/core/patcher.py → py2max-0.3.1/py2max/core/factory.py +529 -695
  10. py2max-0.3.1/py2max/core/patcher.py +596 -0
  11. {py2max-0.2.1 → py2max-0.3.1}/py2max/core/patchline.py +13 -10
  12. py2max-0.3.1/py2max/core/serialization.py +122 -0
  13. {py2max-0.2.1 → py2max-0.3.1}/py2max/export/converters.py +11 -8
  14. {py2max-0.2.1 → py2max-0.3.1}/py2max/export/svg.py +86 -23
  15. {py2max-0.2.1 → py2max-0.3.1}/py2max/layout/base.py +5 -5
  16. {py2max-0.2.1 → py2max-0.3.1}/py2max/layout/flow.py +25 -14
  17. {py2max-0.2.1 → py2max-0.3.1}/py2max/layout/grid.py +26 -18
  18. {py2max-0.2.1 → py2max-0.3.1}/py2max/layout/matrix.py +22 -17
  19. {py2max-0.2.1 → py2max-0.3.1}/py2max/log.py +4 -2
  20. py2max-0.3.1/py2max/m4l.py +626 -0
  21. {py2max-0.2.1 → py2max-0.3.1}/py2max/maxref/__init__.py +15 -3
  22. py2max-0.3.1/py2max/maxref/data/bundle.json.gz +0 -0
  23. {py2max-0.2.1 → py2max-0.3.1}/py2max/maxref/db.py +42 -24
  24. {py2max-0.2.1 → py2max-0.3.1}/py2max/maxref/legacy.py +20 -5
  25. {py2max-0.2.1 → py2max-0.3.1}/py2max/maxref/parser.py +95 -26
  26. {py2max-0.2.1 → py2max-0.3.1}/py2max/utils.py +22 -1
  27. {py2max-0.2.1 → py2max-0.3.1}/pyproject.toml +13 -15
  28. py2max-0.3.1/tests/conftest.py +21 -0
  29. py2max-0.3.1/tests/data/mydevice.amxd +0 -0
  30. py2max-0.3.1/tests/data/mydevice2.amxd +0 -0
  31. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/advanced/data_containers.py +5 -5
  32. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/api/box_api_examples.py +7 -7
  33. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/api/patcher_api_examples.py +12 -12
  34. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/auto_layout_demo.py +14 -14
  35. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/layout/columnar_layout_examples.py +5 -5
  36. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/layout/grid_layout_examples.py +1 -1
  37. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/layout/matrix_layout_examples.py +2 -2
  38. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/preview/svg_preview_demo.py +7 -7
  39. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/quickstart/layout_examples.py +1 -1
  40. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/tutorial/generative_music.py +1 -1
  41. {py2max-0.2.1 → py2max-0.3.1}/tests/test_abstract_coverage.py +1 -1
  42. py2max-0.3.1/tests/test_amxd.py +336 -0
  43. py2max-0.3.1/tests/test_basic.py +22 -0
  44. {py2max-0.2.1 → py2max-0.3.1}/tests/test_bpatcher.py +1 -1
  45. {py2max-0.2.1 → py2max-0.3.1}/tests/test_cli.py +0 -1
  46. py2max-0.3.1/tests/test_colors_theme.py +94 -0
  47. py2max-0.3.1/tests/test_comment.py +8 -0
  48. {py2max-0.2.1 → py2max-0.3.1}/tests/test_connection_validation.py +2 -2
  49. {py2max-0.2.1 → py2max-0.3.1}/tests/test_converters.py +1 -2
  50. {py2max-0.2.1 → py2max-0.3.1}/tests/test_core_coverage.py +25 -11
  51. {py2max-0.2.1 → py2max-0.3.1}/tests/test_db.py +0 -2
  52. {py2max-0.2.1 → py2max-0.3.1}/tests/test_dict.py +1 -1
  53. py2max-0.3.1/tests/test_encapsulate.py +106 -0
  54. {py2max-0.2.1 → py2max-0.3.1}/tests/test_error_handling.py +14 -7
  55. py2max-0.3.1/tests/test_gen.py +96 -0
  56. {py2max-0.2.1 → py2max-0.3.1}/tests/test_layout.py +0 -1
  57. {py2max-0.2.1 → py2max-0.3.1}/tests/test_layout_builtins.py +6 -6
  58. {py2max-0.2.1 → py2max-0.3.1}/tests/test_layout_coverage.py +26 -28
  59. {py2max-0.2.1 → py2max-0.3.1}/tests/test_layout_flow.py +4 -4
  60. {py2max-0.2.1 → py2max-0.3.1}/tests/test_layout_hola1.py +5 -2
  61. {py2max-0.2.1 → py2max-0.3.1}/tests/test_layout_hola_graph.py +1 -1
  62. {py2max-0.2.1 → py2max-0.3.1}/tests/test_layout_matrix.py +0 -1
  63. {py2max-0.2.1 → py2max-0.3.1}/tests/test_layout_networkx2.py +3 -3
  64. {py2max-0.2.1 → py2max-0.3.1}/tests/test_layout_nx_graphviz.py +3 -3
  65. py2max-0.3.1/tests/test_m4l.py +140 -0
  66. py2max-0.3.1/tests/test_maxref_bundle.py +91 -0
  67. {py2max-0.2.1 → py2max-0.3.1}/tests/test_mc_cycle.py +1 -1
  68. py2max-0.3.1/tests/test_mc_poly.py +40 -0
  69. {py2max-0.2.1 → py2max-0.3.1}/tests/test_nested.py +6 -2
  70. py2max-0.3.1/tests/test_nested_patchers.py +50 -0
  71. {py2max-0.2.1 → py2max-0.3.1}/tests/test_number_tilde.py +5 -5
  72. {py2max-0.2.1 → py2max-0.3.1}/tests/test_param.py +2 -2
  73. {py2max-0.2.1 → py2max-0.3.1}/tests/test_patcher.py +9 -5
  74. py2max-0.3.1/tests/test_presets.py +64 -0
  75. {py2max-0.2.1 → py2max-0.3.1}/tests/test_pydantic.py +6 -8
  76. {py2max-0.2.1 → py2max-0.3.1}/tests/test_rnbo.py +1 -2
  77. {py2max-0.2.1 → py2max-0.3.1}/tests/test_search.py +12 -12
  78. {py2max-0.2.1 → py2max-0.3.1}/tests/test_svg.py +48 -10
  79. {py2max-0.2.1 → py2max-0.3.1}/tests/test_table.py +6 -2
  80. {py2max-0.2.1 → py2max-0.3.1}/tests/test_tree_builder.py +3 -1
  81. {py2max-0.2.1 → py2max-0.3.1}/tests/test_tutorial_simple_synthesis.py +1 -1
  82. {py2max-0.2.1 → py2max-0.3.1}/tests/test_two_sines.py +10 -0
  83. py2max-0.3.1/tests/test_validate_attrs.py +66 -0
  84. py2max-0.2.1/py2max/server/__init__.py +0 -54
  85. py2max-0.2.1/py2max/server/client.py +0 -295
  86. py2max-0.2.1/py2max/server/inline.py +0 -312
  87. py2max-0.2.1/py2max/server/repl.py +0 -561
  88. py2max-0.2.1/py2max/server/rpc.py +0 -240
  89. py2max-0.2.1/py2max/server/websocket.py +0 -997
  90. py2max-0.2.1/py2max/static/cola.min.js +0 -4
  91. py2max-0.2.1/py2max/static/d3.v7.min.js +0 -2
  92. py2max-0.2.1/py2max/static/dagre-bundle.js +0 -328
  93. py2max-0.2.1/py2max/static/elk.bundled.js +0 -6663
  94. py2max-0.2.1/py2max/static/index.html +0 -168
  95. py2max-0.2.1/py2max/static/interactive.html +0 -589
  96. py2max-0.2.1/py2max/static/interactive.js +0 -2111
  97. py2max-0.2.1/py2max/static/live-preview.js +0 -324
  98. py2max-0.2.1/py2max/static/svg.min.js +0 -13
  99. py2max-0.2.1/py2max/static/svg.min.js.map +0 -1
  100. py2max-0.2.1/tests/examples/info_command_demo.py +0 -106
  101. py2max-0.2.1/tests/examples/inline_repl_verification.py +0 -64
  102. py2max-0.2.1/tests/examples/interactive_demo.py +0 -228
  103. py2max-0.2.1/tests/examples/interactive_save_demo.py +0 -164
  104. py2max-0.2.1/tests/examples/live_preview_demo.py +0 -225
  105. py2max-0.2.1/tests/examples/refresh_function_verification.py +0 -54
  106. py2max-0.2.1/tests/examples/repl_client_server_demo.py +0 -171
  107. py2max-0.2.1/tests/examples/repl_quickstart.py +0 -140
  108. py2max-0.2.1/tests/test_basic.py +0 -11
  109. py2max-0.2.1/tests/test_comment.py +0 -8
  110. py2max-0.2.1/tests/test_gen.py +0 -27
  111. py2max-0.2.1/tests/test_nested_patchers.py +0 -279
  112. py2max-0.2.1/tests/test_repl.py +0 -309
  113. py2max-0.2.1/tests/test_repl_client.py +0 -348
  114. py2max-0.2.1/tests/test_repl_inline.py +0 -283
  115. py2max-0.2.1/tests/test_repl_server.py +0 -316
  116. py2max-0.2.1/tests/test_websocket.py +0 -232
  117. {py2max-0.2.1 → py2max-0.3.1}/LICENSE +0 -0
  118. {py2max-0.2.1 → py2max-0.3.1}/py2max/__main__.py +0 -0
  119. {py2max-0.2.1 → py2max-0.3.1}/py2max/core/__init__.py +0 -0
  120. {py2max-0.2.1 → py2max-0.3.1}/py2max/core/common.py +0 -0
  121. {py2max-0.2.1 → py2max-0.3.1}/py2max/exceptions.py +0 -0
  122. {py2max-0.2.1 → py2max-0.3.1}/py2max/export/__init__.py +0 -0
  123. {py2max-0.2.1 → py2max-0.3.1}/py2max/layout/__init__.py +0 -0
  124. {py2max-0.2.1 → py2max-0.3.1}/py2max/maxref/category.py +0 -0
  125. {py2max-0.2.1 → py2max-0.3.1}/py2max/py.typed +0 -0
  126. {py2max-0.2.1 → py2max-0.3.1}/py2max/transformers.py +0 -0
  127. {py2max-0.2.1 → py2max-0.3.1}/tests/__init__.py +0 -0
  128. {py2max-0.2.1 → py2max-0.3.1}/tests/data/complex.maxpat +0 -0
  129. {py2max-0.2.1 → py2max-0.3.1}/tests/data/desc.maxpat +0 -0
  130. {py2max-0.2.1 → py2max-0.3.1}/tests/data/empty.maxpat +0 -0
  131. {py2max-0.2.1 → py2max-0.3.1}/tests/data/nested.maxpat +0 -0
  132. {py2max-0.2.1 → py2max-0.3.1}/tests/data/simple.maxpat +0 -0
  133. {py2max-0.2.1 → py2max-0.3.1}/tests/data/tabular.maxpat +0 -0
  134. {py2max-0.2.1 → py2max-0.3.1}/tests/data/umenu.maxref.xml +0 -0
  135. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/README.md +0 -0
  136. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/advanced/connection_patterns.py +0 -0
  137. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/advanced/custom_extensions.py +0 -0
  138. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/advanced/error_handling.py +0 -0
  139. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/advanced/performance_optimization.py +0 -0
  140. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/advanced/subpatchers.py +0 -0
  141. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/db/category_db_demo.py +0 -0
  142. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/db/maxref_db_demo.py +0 -0
  143. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/layout/flow_layout_examples.py +0 -0
  144. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/preview/basic_synth.maxpat +0 -0
  145. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/preview/basic_synth.svg +0 -0
  146. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/preview/complex_synth.maxpat +0 -0
  147. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/preview/complex_synth.svg +0 -0
  148. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/preview/flow_layout.maxpat +0 -0
  149. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/preview/flow_layout.svg +0 -0
  150. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/preview/grid_layout.maxpat +0 -0
  151. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/preview/grid_layout.svg +0 -0
  152. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/preview/horizontal_layout.maxpat +0 -0
  153. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/preview/horizontal_layout.svg +0 -0
  154. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/preview/styled_no_ports.svg +0 -0
  155. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/preview/styled_no_title.svg +0 -0
  156. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/preview/styled_patch.maxpat +0 -0
  157. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/preview/styled_with_ports.svg +0 -0
  158. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/preview/vertical_layout.maxpat +0 -0
  159. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/preview/vertical_layout.svg +0 -0
  160. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/preview/workflow_demo.maxpat +0 -0
  161. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/preview/workflow_demo.svg +0 -0
  162. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/quickstart/basic_patch.py +0 -0
  163. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/tutorial/interactive_controller.py +0 -0
  164. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/tutorial/signal_processing_chain.py +0 -0
  165. {py2max-0.2.1 → py2max-0.3.1}/tests/examples/tutorial/simple_synthesis.py +0 -0
  166. {py2max-0.2.1 → py2max-0.3.1}/tests/graphs/random/v30e33.tglf +0 -0
  167. {py2max-0.2.1 → py2max-0.3.1}/tests/registry.py +0 -0
  168. {py2max-0.2.1 → py2max-0.3.1}/tests/scratch.py +0 -0
  169. {py2max-0.2.1 → py2max-0.3.1}/tests/test_abstraction.py +0 -0
  170. {py2max-0.2.1 → py2max-0.3.1}/tests/test_add.py +0 -0
  171. {py2max-0.2.1 → py2max-0.3.1}/tests/test_attrui.py +0 -0
  172. {py2max-0.2.1 → py2max-0.3.1}/tests/test_beap.py +0 -0
  173. {py2max-0.2.1 → py2max-0.3.1}/tests/test_coll.py +0 -0
  174. {py2max-0.2.1 → py2max-0.3.1}/tests/test_colors.py +0 -0
  175. {py2max-0.2.1 → py2max-0.3.1}/tests/test_defaults.py +0 -0
  176. {py2max-0.2.1 → py2max-0.3.1}/tests/test_examples.py +0 -0
  177. {py2max-0.2.1 → py2max-0.3.1}/tests/test_ezdac.py +0 -0
  178. {py2max-0.2.1 → py2max-0.3.1}/tests/test_group.py +0 -0
  179. {py2max-0.2.1 → py2max-0.3.1}/tests/test_itable.py +0 -0
  180. {py2max-0.2.1 → py2max-0.3.1}/tests/test_js.py +0 -0
  181. {py2max-0.2.1 → py2max-0.3.1}/tests/test_kwds_filter.py +0 -0
  182. {py2max-0.2.1 → py2max-0.3.1}/tests/test_layout_graph_layout.py +0 -0
  183. {py2max-0.2.1 → py2max-0.3.1}/tests/test_layout_hola2.py +0 -0
  184. {py2max-0.2.1 → py2max-0.3.1}/tests/test_layout_hola3.py +0 -0
  185. {py2max-0.2.1 → py2max-0.3.1}/tests/test_layout_networkx1.py +0 -0
  186. {py2max-0.2.1 → py2max-0.3.1}/tests/test_layout_nx_orthogonal.py +0 -0
  187. {py2max-0.2.1 → py2max-0.3.1}/tests/test_layout_nx_tsmpy.py +0 -0
  188. {py2max-0.2.1 → py2max-0.3.1}/tests/test_layout_vertical.py +0 -0
  189. {py2max-0.2.1 → py2max-0.3.1}/tests/test_linking.py +0 -0
  190. {py2max-0.2.1 → py2max-0.3.1}/tests/test_maxref.py +0 -0
  191. {py2max-0.2.1 → py2max-0.3.1}/tests/test_message.py +0 -0
  192. {py2max-0.2.1 → py2max-0.3.1}/tests/test_mypatch.py +0 -0
  193. {py2max-0.2.1 → py2max-0.3.1}/tests/test_numbers.py +0 -0
  194. {py2max-0.2.1 → py2max-0.3.1}/tests/test_pitched_osc.py +0 -0
  195. {py2max-0.2.1 → py2max-0.3.1}/tests/test_rnbo_subpatcher.py +0 -0
  196. {py2max-0.2.1 → py2max-0.3.1}/tests/test_scripting_name.py +0 -0
  197. {py2max-0.2.1 → py2max-0.3.1}/tests/test_semantic_ids.py +0 -0
  198. {py2max-0.2.1 → py2max-0.3.1}/tests/test_subpatch.py +0 -0
  199. {py2max-0.2.1 → py2max-0.3.1}/tests/test_transformers.py +0 -0
  200. {py2max-0.2.1 → py2max-0.3.1}/tests/test_tree.py +0 -0
  201. {py2max-0.2.1 → py2max-0.3.1}/tests/test_umenu.py +0 -0
  202. {py2max-0.2.1 → py2max-0.3.1}/tests/test_utils.py +0 -0
  203. {py2max-0.2.1 → py2max-0.3.1}/tests/test_varname.py +0 -0
  204. {py2max-0.2.1 → py2max-0.3.1}/tests/test_zl_group.py +0 -0
@@ -2,6 +2,107 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.3.1]
6
+
7
+ ### New: Standalone `gen.codebox~` Support
8
+
9
+ - `Patcher.add_gen_codebox(code)` adds a self-contained `gen.codebox~` object -- a complete gen patch in a single box that lives directly in a regular Max patcher, distinct from the inner `codebox~` (emitted by `add_codebox`) that belongs inside a `gen~`/`rnbo~` subpatcher. This is the form emitted by gen transpilers. Code newlines are normalized to CRLF as Max expects, and `fontname`/`fontsize` default to the monospaced gen style.
10
+ - Inlet/outlet counts are derived automatically from the code (the highest `inN` / `outN` references, floor of 1), matching gen's dynamic-I/O semantics. Explicit `numinlets` / `numoutlets` still override.
11
+ - Available via the `add()` string shortcut too: `p.add("gen.codebox~ out1 = in1 * 0.5;")`. The shortcut suits single-line / `;`-terminated code; pass multi-line source to `add_gen_codebox()` directly.
12
+ - Connection validation for `gen.codebox~` (and `codebox` / `codebox~`) now bound-checks against the box's own declared inlet/outlet counts rather than the static `.maxref.xml` entry, since codebox I/O is code-dependent. This both allows valid connections to/from wider codeboxes (e.g. from a second outlet) and rejects genuinely out-of-range ones.
13
+
14
+ ## [0.3.0]
15
+
16
+ ### Removed: Interactive Server Split Into `py2max-server` (breaking)
17
+
18
+ The browser-based live editor and remote REPL have moved to a separate companion package, [`py2max-server`](https://github.com/shakfu/py2max-server), so the core library stays small, offline, and dependency-free.
19
+
20
+ - Removed `Patcher.serve()` and the `py2max serve` / `py2max repl` CLI commands; those CLI subcommands now print a pointer to `py2max-server`.
21
+ - Removed the `[server]` optional-dependency extra (`websockets`, `ptpython`) and the bundled browser assets (`py2max/static/`).
22
+ - Install the server features with `pip install py2max-server` and use `py2max-server serve <patch>` / `py2max-server repl …`. The remote REPL now requires token authentication (passed via `--token` or `PY2MAX_REPL_TOKEN`).
23
+
24
+ ### New: `Patcher.encapsulate()`
25
+
26
+ - `Patcher.encapsulate(boxes, text="p sub")` wraps a selection of boxes into a subpatcher, auto-generating `inlet`/`outlet` objects for any connections that cross the selection boundary and rewiring the parent through the new subpatcher box. Connections wholly inside the selection move into the subpatcher; connections wholly outside it are untouched. Ports are de-duplicated by source, matching how patches are built by hand. Returns the new subpatcher `Box`.
27
+
28
+ ### New: Preset / `pattrstorage` Scaffolding
29
+
30
+ - `Patcher.add_pattrstorage(name)`, `Patcher.add_autopattr()`, and `Patcher.add_preset_system(name)` (which adds both and wires `autopattr` -> `pattrstorage`) scaffold a Max preset system. Any object with a scripting name (`varname`) or `parameter_enable=1` participates.
31
+ - `Patcher.enable_parameter(box, longname, shortname="", ptype=0, initial=None)` turns an existing UI box into a Max parameter (sets `parameter_enable` and the `saved_attribute_attributes`), so it participates in presets and, in a Max for Live device, appears as an automatable parameter.
32
+
33
+ ### New: Keyword-Attribute Validation (`validate_attrs`)
34
+
35
+ - `Patcher(validate_attrs=True)` warns (`UserWarning`) when an object is given a keyword that is not a known attribute for its Max class -- catching typos like `inital=` for `initial=`. The known set is the object's maxref attributes plus a universal box-attribute whitelist; objects with no maxref entry are skipped. Off by default and warn-only, so it never changes generated output.
36
+
37
+ ### New: Multichannel (`mc.`) / Polyphony Helpers
38
+
39
+ - `Patcher.add_mc(text, chans=None)` adds a multichannel object, prefixing `mc.` and appending `@chans` (e.g. `add_mc("cycle~ 440", chans=4)` -> `mc.cycle~ 440 @chans 4`).
40
+ - `Patcher.add_poly(target, voices=1)` adds a `poly~` object hosting N voices of a target patch.
41
+
42
+ ### Improved: SVG Export (Max-faithful preview)
43
+
44
+ - The `preview` / `to_svg` output now approximates Max's look: a light patcher background, signal vs message/control **ports colored distinctly** (signal green, control dark), signal **cables drawn thicker and in a distinct color**, and subpatcher boxes tinted so they stand out. Object text is intentionally not truncated, matching Max (objects size to their text).
45
+
46
+ ### Changed: Documentation moved to MkDocs
47
+
48
+ - Documentation migrated from Sphinx/reStructuredText to **MkDocs + Material + mkdocstrings** (all Markdown, matching the rest of the repo). The API reference is generated from the (now fully typed) docstrings, including `Patcher`'s mixin-provided methods. The changelog and contributing pages are single-source includes of `CHANGELOG.md` / `CONTRIBUTING.md`. Build with `make docs`, preview with `make docs-serve`, publish with `make docs-deploy`. `docs/notes/` is retained as a historical journal but excluded from the published site.
49
+
50
+ ### New: Color / Theme Helpers
51
+
52
+ - `Box.set_color(bg=..., text=..., border=...)` sets a box's `bgcolor`/`textcolor`/`bordercolor`; each accepts a named color (e.g. `"red"`), a hex string (`"#ff8800"`), or an `[r, g, b(, a)]` float sequence. Returns the box for chaining.
53
+ - `Patcher.apply_theme(theme)` applies a color theme to every box (recursing into subpatchers). Built-in themes: `"light"`, `"dark"`, `"blue"`, `"high-contrast"`; or pass a dict of `bg`/`text`/`border` colors.
54
+ - `py2max.core.colors` exposes the `MAX_COLORS` named palette and `resolve_color()`.
55
+
56
+ ### Security
57
+
58
+ - **Removed a misleading path-traversal check** in `Patcher.save_as()`. The previous `..`/`/etc` allowlist was trivially bypassable and gave a false sense of safety; for an offline file generator it provided no real protection. Genuinely unresolvable paths still raise `PatcherIOError`.
59
+
60
+ ### Typed: Full `mypy --strict`
61
+
62
+ - The entire package is now annotated and passes `mypy --strict`, backing the shipped `py.typed` marker. `[tool.mypy]` enforces `strict = true`. Core has **no runtime dependencies**.
63
+
64
+ ### Changed: Lighter Core Imports
65
+
66
+ - `import py2max` no longer eagerly imports `sqlite3`, the maxref database layer, or `py2max.m4l`. `MaxRefDB` is now available lazily via `from py2max.maxref import MaxRefDB` (removed from the top-level `py2max` namespace).
67
+
68
+ ### Internal: `Patcher` Decomposition
69
+
70
+ - Split the ~1660-line `Patcher` class into focused mixins composed via inheritance: object creation (`BoxFactoryMixin` in `core/factory.py`) and serialization (`SerializationMixin` in `core/serialization.py`). The public API is unchanged; adding a new object type now means editing `core/factory.py` rather than the core class.
71
+
72
+ ### Fixed
73
+
74
+ - Object-name resolution (used by connection validation and object classification) now reads the box `text` property, so it resolves correctly for boxes loaded from a file. Previously it inspected only programmatic kwargs and returned `newobj` for loaded boxes.
75
+ - `Box.oid` now returns the trailing numeric part of any id (e.g. `cycle_1` -> 1) instead of raising `ValueError` under `semantic_ids=True`.
76
+ - The `py2max` CLI now reports all `Py2MaxError`s (not just `InvalidConnectionError`) as a clean error message instead of leaking a traceback.
77
+ - Fixed an `inital` -> `initial` keyword typo in the simple-synthesis tutorial.
78
+
79
+ ### Testing & Tooling
80
+
81
+ - The test suite is now hermetic: a `conftest.py` autouse fixture isolates each test in a temporary working directory, so relative `outputs/` writes no longer accumulate in the repo. Fixture reads are anchored at the test file.
82
+ - Promoted the `.amxd` byte-for-byte fixtures from the gitignored `outputs/` into tracked `tests/data/`, so that verification runs in CI and on fresh checkouts instead of only on the author's machine.
83
+ - Repo-wide `ruff` lint and format cleanup.
84
+
85
+ ### New: Max for Live Support (`py2max.m4l`)
86
+
87
+ Implements [issue #9](https://github.com/shakfu/py2max/issues/9). See [`docs/notes/amxd.md`](https://github.com/shakfu/py2max/blob/main/docs/notes/amxd.md) for the on-disk format, embedded-project block, and verification details.
88
+
89
+ - **`.amxd` read/write**: byte-for-byte compatible with Max-exported devices; verified against real fixtures and end-to-end in Live 12.
90
+ - **Device-type discrimination**: Audio Effect / Instrument / MIDI Effect via `Patcher(device_type=...)` or the `pack_amxd` / `write_amxd` `device_type` argument.
91
+ - **Presentation-mode helpers**: `Patcher.enable_presentation(devicewidth=...)`, `Patcher.enforce_integer_coords()`, `Box.add_to_presentation([x, y, w, h])` (rejects M4L infrastructure objects, rounds fractional coords with a warning).
92
+ - `Patcher.save()` / `Patcher.from_file()` auto-detect the `.amxd` extension; `.maxpat` path is unchanged.
93
+
94
+ ### Changed: M4L Module Layout & Imports
95
+
96
+ - All M4L code (binary format + presentation helpers) lives in a single module `py2max/m4l.py`. Previously briefly split as `py2max/amxd.py`.
97
+ - M4L symbols are reachable only via `from py2max.m4l import …`; nothing is re-exported from the top-level `py2max` namespace.
98
+
99
+ ### New: Prebuilt MaxRef Bundle (Linux Support)
100
+
101
+ - Ship `py2max/maxref/data/bundle.json.gz` in the wheel (1175 objects, ~1 MiB compressed, ~7 MiB raw).
102
+ - `MaxRefCache._get_refdict()` falls back to the bundle when no local Max installation is found, pre-seeding the parser cache so `Box.help()`, `get_inlet_count`, `get_outlet_count`, and connection validation work identically on Linux.
103
+ - Regenerate with `uv run python scripts/build_maxref_bundle.py` on a machine with Max installed; commit the result.
104
+ - Bundle stores full parsed data (methods, attributes, inlets/outlets, digests, descriptions) — not a trimmed subset — so introspection parity with macOS/Windows is preserved.
105
+
5
106
  ## [0.2.1] - 2026-01-11
6
107
 
7
108
  ### New: Dagre Layout Algorithm
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: py2max
3
- Version: 0.2.1
3
+ Version: 0.3.1
4
4
  Summary: A library for offline generation of Max/MSP patcher (.maxpat) files.
5
5
  Keywords: Max,maxpat,offline
6
6
  Author: Shakeeb Alireza
@@ -12,7 +12,6 @@ Classifier: Intended Audience :: Developers
12
12
  Classifier: Intended Audience :: End Users/Desktop
13
13
  Classifier: Topic :: Multimedia :: Sound/Audio
14
14
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
- Classifier: License :: OSI Approved :: MIT License
16
15
  Classifier: Programming Language :: Python :: 3
17
16
  Classifier: Programming Language :: Python :: 3.9
18
17
  Classifier: Programming Language :: Python :: 3.10
@@ -22,15 +21,14 @@ Classifier: Programming Language :: Python :: 3.13
22
21
  Classifier: Programming Language :: Python :: 3.14
23
22
  Classifier: Operating System :: OS Independent
24
23
  Classifier: Typing :: Typed
25
- Requires-Dist: websockets>=12.0 ; extra == 'server'
26
- Requires-Dist: ptpython>=3.0.0 ; extra == 'server'
24
+ Maintainer: Shakeeb Alireza
25
+ Maintainer-email: Shakeeb Alireza <shakfu@users.noreply.github.com>
27
26
  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
27
  Project-URL: Homepage, https://github.com/shakfu/py2max
31
- Project-URL: Issues, https://github.com/shakfu/py2max/issues
28
+ Project-URL: Documentation, https://github.com/shakfu/py2max#readme
32
29
  Project-URL: Repository, https://github.com/shakfu/py2max.git
33
- Provides-Extra: server
30
+ Project-URL: Issues, https://github.com/shakfu/py2max/issues
31
+ Project-URL: Changelog, https://github.com/shakfu/py2max/blob/main/CHANGELOG.md
34
32
  Description-Content-Type: text/markdown
35
33
 
36
34
  # py2max
@@ -46,9 +44,13 @@ If you are looking for Python 3 externals for Max/MSP, check out the [py-js](htt
46
44
 
47
45
  ```bash
48
46
  pip install py2max
47
+ ```
48
+
49
+ For the browser-based live editor and remote REPL, install the companion
50
+ [`py2max-server`](https://github.com/shakfu/py2max-server) package:
49
51
 
50
- # With interactive server support
51
- pip install py2max[server]
52
+ ```bash
53
+ pip install py2max-server
52
54
  ```
53
55
 
54
56
  For development:
@@ -82,15 +84,48 @@ That's it! Open `my-synth.maxpat` in Max to see your patch.
82
84
 
83
85
  - **Offline Patch Generation** - Create Max patches programmatically without Max running
84
86
  - **Round-trip Conversion** - Load, modify, and save existing `.maxpat` files
87
+ - **Max for Live (.amxd)** - Read/write binary `.amxd` device files with presentation-mode helpers
85
88
  - **Universal Object Support** - Works with any Max/MSP/Jitter object
86
- - **99% Test Coverage** - 418+ tests ensure reliability
89
+ - **Fully typed** - Passes `mypy --strict`; no runtime dependencies
90
+ - **High Test Coverage** - 420+ tests ensure reliability
91
+
92
+ ### Max for Live (.amxd)
93
+
94
+ Generate Max for Live devices directly. `Patcher.save()` / `Patcher.from_file()`
95
+ auto-detect the `.amxd` extension and read/write the binary device format,
96
+ byte-for-byte compatible with Max-exported devices.
97
+
98
+ ```python
99
+ from py2max import Patcher
100
+
101
+ # device_type: "audio_effect" (default), "instrument", or "midi_effect"
102
+ p = Patcher('gain.amxd', device_type='audio_effect')
103
+ p.enable_presentation(devicewidth=120) # render Ableton's device strip
87
104
 
88
- ### Interactive Server (New in 0.2.x)
105
+ plugin = p.add_textbox('plugin~') # audio in from Live
106
+ gain = p.add('live.gain~', maxclass='live.gain~')
107
+ plugout = p.add_textbox('plugout~') # audio back to Live
108
+ gain.add_to_presentation([20, 20, 60, 136]) # show the fader in the device
89
109
 
90
- Real-time browser-based patch editing with bidirectional sync:
110
+ p.add_line(plugin, gain, outlet=0, inlet=0)
111
+ p.add_line(gain, plugout, outlet=0, inlet=0)
112
+ p.save() # writes a binary .amxd
113
+ ```
114
+
115
+ Helpers: `Patcher.enable_presentation(devicewidth=...)`,
116
+ `Box.add_to_presentation([x, y, w, h])` (rejects M4L infrastructure objects and
117
+ rounds fractional coordinates), and `Patcher.enforce_integer_coords()`. M4L
118
+ binary helpers live in `py2max.m4l`.
119
+
120
+ ### Interactive Server (separate package)
121
+
122
+ Real-time browser-based patch editing with bidirectional sync lives in the
123
+ companion [`py2max-server`](https://github.com/shakfu/py2max-server) package, so
124
+ the core library stays small and offline:
91
125
 
92
126
  ```bash
93
- py2max serve my-patch.maxpat
127
+ pip install py2max-server
128
+ py2max-server serve my-patch.maxpat
94
129
  # Opens browser at http://localhost:8000
95
130
  ```
96
131
 
@@ -247,6 +282,34 @@ p.link(sbox, dac)
247
282
  p.save()
248
283
  ```
249
284
 
285
+ ### Gen Codebox
286
+
287
+ `add_gen_codebox()` adds a standalone `gen.codebox~` object -- a complete gen
288
+ patch in a single box that sits directly in a regular Max patcher (unlike the
289
+ inner `codebox~` from `add_codebox()`, which belongs inside a `gen~`/`rnbo~`
290
+ subpatcher). Inlet/outlet counts are derived automatically from the highest
291
+ `inN`/`outN` references in the code:
292
+
293
+ ```python
294
+ p = Patcher('fbdelay.maxpat')
295
+
296
+ # 1 inlet (in1), 1 outlet (out1)
297
+ osc = p.add('cycle~ 440')
298
+ cb = p.add_gen_codebox('''
299
+ Param feedback(0.5, min=0.0, max=0.95);
300
+ History fb(0.0);
301
+ out1 = in1 + fb * feedback;
302
+ fb = out1;
303
+ ''')
304
+ dac = p.add('ezdac~')
305
+ p.link(osc, cb)
306
+ p.link(cb, dac)
307
+ p.save()
308
+
309
+ # Or via the add() string shortcut (single-line / `;`-terminated code)
310
+ cb = p.add('gen.codebox~ out1 = in1 * 0.5;')
311
+ ```
312
+
250
313
  ### Object Search
251
314
 
252
315
  ```python
@@ -285,12 +348,15 @@ py2max validate demo.maxpat
285
348
 
286
349
  ### Interactive Server
287
350
 
351
+ Provided by the separate [`py2max-server`](https://github.com/shakfu/py2max-server)
352
+ package (`pip install py2max-server`):
353
+
288
354
  ```bash
289
355
  # Start server with browser editing
290
- py2max serve my-patch.maxpat
356
+ py2max-server serve my-patch.maxpat
291
357
 
292
358
  # With REPL in same terminal
293
- py2max serve my-patch.maxpat --repl
359
+ py2max-server serve my-patch.maxpat --repl
294
360
  ```
295
361
 
296
362
  ### MaxRef Database
@@ -350,18 +416,21 @@ All classes are extendable via `**kwargs`, allowing any Max object configuration
350
416
 
351
417
  ## Caveats
352
418
 
353
- - Max doesn't refresh from file when open - close and reopen to see changes, or use `py2max serve` for live editing
419
+ - 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
354
420
  - For tilde variants, use the `_tilde` suffix: `p.add_gen()` vs `p.add_gen_tilde()`
355
421
  - API docs in progress - see `CLAUDE.md` for comprehensive usage
356
422
 
357
423
  ## Examples
358
424
 
359
- See the `examples/` directory for demonstrations:
425
+ The [`tests/examples/`](tests/examples/) directory contains working, tested
426
+ examples organized by topic (see its [README](tests/examples/README.md)):
360
427
 
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
428
+ - `quickstart/basic_patch.py` - Simple oscillator patch
429
+ - `tutorial/signal_processing_chain.py` - Complex audio processing chain
430
+ - `tutorial/generative_music.py` - Generative music system with patterns
431
+ - `layout/grid_layout_examples.py` - Grid layout with clustering
432
+ - `advanced/data_containers.py` - Tables, collections, and dictionaries
433
+ - `api/patcher_api_examples.py` - Patcher API reference examples
365
434
 
366
435
  External usage:
367
436
 
@@ -11,9 +11,13 @@ If you are looking for Python 3 externals for Max/MSP, check out the [py-js](htt
11
11
 
12
12
  ```bash
13
13
  pip install py2max
14
+ ```
15
+
16
+ For the browser-based live editor and remote REPL, install the companion
17
+ [`py2max-server`](https://github.com/shakfu/py2max-server) package:
14
18
 
15
- # With interactive server support
16
- pip install py2max[server]
19
+ ```bash
20
+ pip install py2max-server
17
21
  ```
18
22
 
19
23
  For development:
@@ -47,15 +51,48 @@ That's it! Open `my-synth.maxpat` in Max to see your patch.
47
51
 
48
52
  - **Offline Patch Generation** - Create Max patches programmatically without Max running
49
53
  - **Round-trip Conversion** - Load, modify, and save existing `.maxpat` files
54
+ - **Max for Live (.amxd)** - Read/write binary `.amxd` device files with presentation-mode helpers
50
55
  - **Universal Object Support** - Works with any Max/MSP/Jitter object
51
- - **99% Test Coverage** - 418+ tests ensure reliability
56
+ - **Fully typed** - Passes `mypy --strict`; no runtime dependencies
57
+ - **High Test Coverage** - 420+ tests ensure reliability
58
+
59
+ ### Max for Live (.amxd)
60
+
61
+ Generate Max for Live devices directly. `Patcher.save()` / `Patcher.from_file()`
62
+ auto-detect the `.amxd` extension and read/write the binary device format,
63
+ byte-for-byte compatible with Max-exported devices.
64
+
65
+ ```python
66
+ from py2max import Patcher
67
+
68
+ # device_type: "audio_effect" (default), "instrument", or "midi_effect"
69
+ p = Patcher('gain.amxd', device_type='audio_effect')
70
+ p.enable_presentation(devicewidth=120) # render Ableton's device strip
71
+
72
+ plugin = p.add_textbox('plugin~') # audio in from Live
73
+ gain = p.add('live.gain~', maxclass='live.gain~')
74
+ plugout = p.add_textbox('plugout~') # audio back to Live
75
+ gain.add_to_presentation([20, 20, 60, 136]) # show the fader in the device
76
+
77
+ p.add_line(plugin, gain, outlet=0, inlet=0)
78
+ p.add_line(gain, plugout, outlet=0, inlet=0)
79
+ p.save() # writes a binary .amxd
80
+ ```
52
81
 
53
- ### Interactive Server (New in 0.2.x)
82
+ Helpers: `Patcher.enable_presentation(devicewidth=...)`,
83
+ `Box.add_to_presentation([x, y, w, h])` (rejects M4L infrastructure objects and
84
+ rounds fractional coordinates), and `Patcher.enforce_integer_coords()`. M4L
85
+ binary helpers live in `py2max.m4l`.
54
86
 
55
- Real-time browser-based patch editing with bidirectional sync:
87
+ ### Interactive Server (separate package)
88
+
89
+ Real-time browser-based patch editing with bidirectional sync lives in the
90
+ companion [`py2max-server`](https://github.com/shakfu/py2max-server) package, so
91
+ the core library stays small and offline:
56
92
 
57
93
  ```bash
58
- py2max serve my-patch.maxpat
94
+ pip install py2max-server
95
+ py2max-server serve my-patch.maxpat
59
96
  # Opens browser at http://localhost:8000
60
97
  ```
61
98
 
@@ -212,6 +249,34 @@ p.link(sbox, dac)
212
249
  p.save()
213
250
  ```
214
251
 
252
+ ### Gen Codebox
253
+
254
+ `add_gen_codebox()` adds a standalone `gen.codebox~` object -- a complete gen
255
+ patch in a single box that sits directly in a regular Max patcher (unlike the
256
+ inner `codebox~` from `add_codebox()`, which belongs inside a `gen~`/`rnbo~`
257
+ subpatcher). Inlet/outlet counts are derived automatically from the highest
258
+ `inN`/`outN` references in the code:
259
+
260
+ ```python
261
+ p = Patcher('fbdelay.maxpat')
262
+
263
+ # 1 inlet (in1), 1 outlet (out1)
264
+ osc = p.add('cycle~ 440')
265
+ cb = p.add_gen_codebox('''
266
+ Param feedback(0.5, min=0.0, max=0.95);
267
+ History fb(0.0);
268
+ out1 = in1 + fb * feedback;
269
+ fb = out1;
270
+ ''')
271
+ dac = p.add('ezdac~')
272
+ p.link(osc, cb)
273
+ p.link(cb, dac)
274
+ p.save()
275
+
276
+ # Or via the add() string shortcut (single-line / `;`-terminated code)
277
+ cb = p.add('gen.codebox~ out1 = in1 * 0.5;')
278
+ ```
279
+
215
280
  ### Object Search
216
281
 
217
282
  ```python
@@ -250,12 +315,15 @@ py2max validate demo.maxpat
250
315
 
251
316
  ### Interactive Server
252
317
 
318
+ Provided by the separate [`py2max-server`](https://github.com/shakfu/py2max-server)
319
+ package (`pip install py2max-server`):
320
+
253
321
  ```bash
254
322
  # Start server with browser editing
255
- py2max serve my-patch.maxpat
323
+ py2max-server serve my-patch.maxpat
256
324
 
257
325
  # With REPL in same terminal
258
- py2max serve my-patch.maxpat --repl
326
+ py2max-server serve my-patch.maxpat --repl
259
327
  ```
260
328
 
261
329
  ### MaxRef Database
@@ -315,18 +383,21 @@ All classes are extendable via `**kwargs`, allowing any Max object configuration
315
383
 
316
384
  ## Caveats
317
385
 
318
- - Max doesn't refresh from file when open - close and reopen to see changes, or use `py2max serve` for live editing
386
+ - 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
319
387
  - For tilde variants, use the `_tilde` suffix: `p.add_gen()` vs `p.add_gen_tilde()`
320
388
  - API docs in progress - see `CLAUDE.md` for comprehensive usage
321
389
 
322
390
  ## Examples
323
391
 
324
- See the `examples/` directory for demonstrations:
392
+ The [`tests/examples/`](tests/examples/) directory contains working, tested
393
+ examples organized by topic (see its [README](tests/examples/README.md)):
325
394
 
326
- - `auto_layout_demo.py` - Complex synthesizer with layout optimization
327
- - `nested_patcher_demo.py` - Subpatcher navigation
328
- - `columnar_layout_demo.py` - Functional column organization
329
- - `matrix_layout_demo.py` - Signal chain matrix layout
395
+ - `quickstart/basic_patch.py` - Simple oscillator patch
396
+ - `tutorial/signal_processing_chain.py` - Complex audio processing chain
397
+ - `tutorial/generative_music.py` - Generative music system with patterns
398
+ - `layout/grid_layout_examples.py` - Grid layout with clustering
399
+ - `advanced/data_containers.py` - Tables, collections, and dictionaries
400
+ - `api/patcher_api_examples.py` - Patcher API reference examples
330
401
 
331
402
  External usage:
332
403
 
@@ -8,7 +8,10 @@ Main Classes:
8
8
  Patcher: Core class for creating and managing Max patches
9
9
  Box: Represents individual Max objects (oscillators, effects, etc.)
10
10
  Patchline: Represents connections between objects
11
- MaxRefDB: SQLite database for Max object reference data
11
+
12
+ The SQLite Max-reference database is available as ``from py2max.maxref import
13
+ MaxRefDB`` -- kept out of the top-level import so ``import py2max`` does not pull
14
+ in ``sqlite3`` and the database layer.
12
15
 
13
16
  Exceptions:
14
17
  Py2MaxError: Base exception for all py2max errors
@@ -32,10 +35,9 @@ Example:
32
35
  >>> p.save()
33
36
  """
34
37
 
35
- __version__ = "0.2.1"
38
+ __version__ = "0.3.1"
36
39
 
37
40
  from .core import Box, Patcher, Patchline
38
- from .maxref import MaxRefDB
39
41
  from .exceptions import (
40
42
  DatabaseError,
41
43
  InvalidConnectionError,
@@ -54,7 +56,6 @@ __all__ = [
54
56
  "Patcher",
55
57
  "Box",
56
58
  "Patchline",
57
- "MaxRefDB",
58
59
  # Exceptions
59
60
  "Py2MaxError",
60
61
  "InvalidConnectionError",