py2pd 0.1.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.
py2pd-0.1.1/PKG-INFO ADDED
@@ -0,0 +1,238 @@
1
+ Metadata-Version: 2.4
2
+ Name: py2pd
3
+ Version: 0.1.1
4
+ Summary: Roundtrip parsing and generation of pure-data patches from python
5
+ Keywords: puredata,pd,audio,dsp,music,synthesis,patching
6
+ Author: Shakeeb Alireza
7
+ Author-email: Shakeeb Alireza <shakfu@users.noreply.github.com>
8
+ License-Expression: MIT
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: End Users/Desktop
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: Implementation :: CPython
18
+ Classifier: Topic :: Multimedia :: Sound/Audio
19
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Sound Synthesis
20
+ Classifier: Topic :: Software Development :: Code Generators
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.13
24
+ Project-URL: Homepage, https://github.com/shakfu/py2pd
25
+ Project-URL: Repository, https://github.com/shakfu/py2pd
26
+ Project-URL: Issues, https://github.com/shakfu/py2pd/issues
27
+ Project-URL: Changelog, https://github.com/shakfu/py2pd/blob/main/CHANGELOG.md
28
+ Description-Content-Type: text/markdown
29
+
30
+ # py2pd - Python <-> PureData
31
+
32
+ Roundtrip parsing and generation of [pure-data](https://puredata.info) patches from python.
33
+
34
+ py2pd is a fork and extensive rewrite of Dylan Burati's [puredata-compiler](https://github.com/dylanburati/puredata-compiler) using some of the ideas from [py2max](https://github.com/shakfu/py2max).
35
+
36
+ ## Install
37
+
38
+ ```bash
39
+ pip install py2pd
40
+ ```
41
+
42
+ ## Quick Start
43
+
44
+ ```python
45
+ from py2pd import Patcher
46
+
47
+ # Create a simple synthesizer patch
48
+ p = Patcher('synth.pd')
49
+
50
+ osc = p.add('osc~ 440')
51
+ gain = p.add('*~ 0.3')
52
+ dac = p.add('dac~')
53
+
54
+ p.link(osc, gain)
55
+ p.link(gain, dac)
56
+ p.link(gain, dac, inlet=1) # stereo
57
+
58
+ p.save()
59
+ ```
60
+
61
+ ## Builder API
62
+
63
+ The `Patcher` class provides methods to add nodes and connect them.
64
+
65
+ ### Adding Nodes
66
+
67
+ ```python
68
+ from py2pd import Patcher
69
+
70
+ p = Patcher('example.pd')
71
+
72
+ # Objects
73
+ osc = p.add('osc~ 440')
74
+ filter_obj = p.add('lop~ 1000')
75
+
76
+ # Messages
77
+ bang = p.add_msg('bang')
78
+ freq_msg = p.add_msg('440')
79
+
80
+ # GUI Elements
81
+ slider = p.add_hslider(min_val=20, max_val=20000, width=150)
82
+ toggle = p.add_toggle(default_value=1, send='onoff')
83
+ numbox = p.add_numberbox(min_val=0, max_val=127)
84
+ bang_btn = p.add_bang(send='trigger', label='Click')
85
+ ```
86
+
87
+ ### Connecting Nodes
88
+
89
+ Use `link()` to connect nodes. By default, outlet 0 connects to inlet 0:
90
+
91
+ ```python
92
+ p.link(osc, gain) # outlet 0 -> inlet 0
93
+ p.link(gain, dac) # left channel
94
+ p.link(gain, dac, inlet=1) # right channel (stereo)
95
+ p.link(trigger, pack, outlet=1, inlet=2) # specific ports
96
+ ```
97
+
98
+ ### Subpatches
99
+
100
+ Create reusable subpatches:
101
+
102
+ ```python
103
+ def make_envelope() -> Patcher:
104
+ p = Patcher()
105
+ inlet = p.add('inlet')
106
+ vline = p.add('vline~')
107
+ outlet = p.add('outlet~')
108
+ p.link(inlet, vline)
109
+ p.link(vline, outlet)
110
+ return p
111
+
112
+ main = Patcher('main.pd')
113
+ osc = main.add('osc~ 440')
114
+ env = main.add_subpatch('envelope', make_envelope())
115
+ vca = main.add('*~')
116
+
117
+ main.link(osc, vca)
118
+ main.link(env, vca, inlet=1)
119
+ ```
120
+
121
+ ### Layout Options
122
+
123
+ **Default layout** - nodes flow top-to-bottom:
124
+ ```python
125
+ p = Patcher('patch.pd')
126
+ p.add('osc~ 440') # Row 1
127
+ p.add('*~ 0.5') # Row 2
128
+ p.add('dac~') # Row 3
129
+ ```
130
+
131
+ **Grid layout** - organized columns:
132
+ ```python
133
+ from py2pd import Patcher, GridLayoutManager
134
+
135
+ grid = GridLayoutManager(columns=4, cell_width=80, cell_height=35)
136
+ p = Patcher('grid.pd', layout=grid)
137
+ ```
138
+
139
+ **Auto layout** - arrange by signal flow:
140
+ ```python
141
+ p = Patcher('patch.pd')
142
+ # Add nodes in any order...
143
+ p.auto_layout(margin=50, row_spacing=50, col_spacing=100)
144
+ ```
145
+
146
+ ### Saving and Export
147
+
148
+ ```python
149
+ p.save() # Save to filename from constructor
150
+ p.save('other.pd') # Save to specific file
151
+ p.save_svg('patch.svg') # Export visualization as SVG
152
+ svg_str = p.to_svg() # Get SVG as string
153
+ ```
154
+
155
+ ### Validation
156
+
157
+ ```python
158
+ p.validate_connections(check_cycles=True) # Raises on invalid connections
159
+ ```
160
+
161
+ ## AST API (Round-trip Parsing)
162
+
163
+ For modifying existing patches with immutable AST nodes:
164
+
165
+ ```python
166
+ from py2pd import parse_file, serialize
167
+
168
+ # Parse existing patch
169
+ ast = parse_file('input.pd')
170
+
171
+ # Modify the AST...
172
+
173
+ # Write back
174
+ with open('output.pd', 'w') as f:
175
+ f.write(serialize(ast))
176
+ ```
177
+
178
+ ### Converting Between APIs
179
+
180
+ You can convert between AST and Builder representations:
181
+
182
+ ```python
183
+ from py2pd import parse_file, to_builder, from_builder
184
+
185
+ # AST -> Builder: parse then edit with the more convenient API
186
+ ast = parse_file('input.pd')
187
+ patch = to_builder(ast)
188
+ patch.add('osc~ 880')
189
+ patch.save('output.pd')
190
+
191
+ # Builder -> AST: for analysis or transformation
192
+ ast = from_builder(patch)
193
+ ```
194
+
195
+ ### When to Use Each API
196
+
197
+ | Use Case | Recommended API |
198
+ |----------|-----------------|
199
+ | Creating patches from scratch | Builder |
200
+ | Modifying existing patches | Builder (via `to_builder()`) |
201
+ | Lossless round-trip of complex patches | AST |
202
+ | Building analysis/refactoring tools | AST |
203
+ | Batch search/replace across .pd files | AST |
204
+
205
+ For most workflows, parse to AST then convert to Builder for editing. Use the AST API directly when you need to preserve elements the Builder doesn't model (e.g., `coords`, comments) or need immutable transformations.
206
+
207
+ AST node types are available from the `py2pd.ast` module:
208
+
209
+ ```python
210
+ from py2pd.ast import PdPatch, PdObj, PdMsg, Position, transform, find_objects
211
+ ```
212
+
213
+ ## GUI Elements
214
+
215
+ | Method | Description |
216
+ |--------|-------------|
217
+ | `add_bang()` | Bang button |
218
+ | `add_toggle()` | On/off toggle |
219
+ | `add_numberbox()` | Editable number |
220
+ | `add_float()` | Float atom |
221
+ | `add_symbol()` | Symbol/text input |
222
+ | `add_hslider()` | Horizontal slider |
223
+ | `add_vslider()` | Vertical slider |
224
+ | `add_hradio()` | Horizontal radio buttons |
225
+ | `add_vradio()` | Vertical radio buttons |
226
+ | `add_canvas()` | Background/label area |
227
+ | `add_vu()` | VU meter |
228
+
229
+ ## Error Handling
230
+
231
+ ```python
232
+ from py2pd import (
233
+ ConnectionError, # Invalid connection arguments
234
+ NodeNotFoundError, # Node not in patch
235
+ InvalidConnectionError, # Bad inlet/outlet index
236
+ CycleWarning, # Feedback loop detected
237
+ )
238
+ ```
py2pd-0.1.1/README.md ADDED
@@ -0,0 +1,209 @@
1
+ # py2pd - Python <-> PureData
2
+
3
+ Roundtrip parsing and generation of [pure-data](https://puredata.info) patches from python.
4
+
5
+ py2pd is a fork and extensive rewrite of Dylan Burati's [puredata-compiler](https://github.com/dylanburati/puredata-compiler) using some of the ideas from [py2max](https://github.com/shakfu/py2max).
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install py2pd
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```python
16
+ from py2pd import Patcher
17
+
18
+ # Create a simple synthesizer patch
19
+ p = Patcher('synth.pd')
20
+
21
+ osc = p.add('osc~ 440')
22
+ gain = p.add('*~ 0.3')
23
+ dac = p.add('dac~')
24
+
25
+ p.link(osc, gain)
26
+ p.link(gain, dac)
27
+ p.link(gain, dac, inlet=1) # stereo
28
+
29
+ p.save()
30
+ ```
31
+
32
+ ## Builder API
33
+
34
+ The `Patcher` class provides methods to add nodes and connect them.
35
+
36
+ ### Adding Nodes
37
+
38
+ ```python
39
+ from py2pd import Patcher
40
+
41
+ p = Patcher('example.pd')
42
+
43
+ # Objects
44
+ osc = p.add('osc~ 440')
45
+ filter_obj = p.add('lop~ 1000')
46
+
47
+ # Messages
48
+ bang = p.add_msg('bang')
49
+ freq_msg = p.add_msg('440')
50
+
51
+ # GUI Elements
52
+ slider = p.add_hslider(min_val=20, max_val=20000, width=150)
53
+ toggle = p.add_toggle(default_value=1, send='onoff')
54
+ numbox = p.add_numberbox(min_val=0, max_val=127)
55
+ bang_btn = p.add_bang(send='trigger', label='Click')
56
+ ```
57
+
58
+ ### Connecting Nodes
59
+
60
+ Use `link()` to connect nodes. By default, outlet 0 connects to inlet 0:
61
+
62
+ ```python
63
+ p.link(osc, gain) # outlet 0 -> inlet 0
64
+ p.link(gain, dac) # left channel
65
+ p.link(gain, dac, inlet=1) # right channel (stereo)
66
+ p.link(trigger, pack, outlet=1, inlet=2) # specific ports
67
+ ```
68
+
69
+ ### Subpatches
70
+
71
+ Create reusable subpatches:
72
+
73
+ ```python
74
+ def make_envelope() -> Patcher:
75
+ p = Patcher()
76
+ inlet = p.add('inlet')
77
+ vline = p.add('vline~')
78
+ outlet = p.add('outlet~')
79
+ p.link(inlet, vline)
80
+ p.link(vline, outlet)
81
+ return p
82
+
83
+ main = Patcher('main.pd')
84
+ osc = main.add('osc~ 440')
85
+ env = main.add_subpatch('envelope', make_envelope())
86
+ vca = main.add('*~')
87
+
88
+ main.link(osc, vca)
89
+ main.link(env, vca, inlet=1)
90
+ ```
91
+
92
+ ### Layout Options
93
+
94
+ **Default layout** - nodes flow top-to-bottom:
95
+ ```python
96
+ p = Patcher('patch.pd')
97
+ p.add('osc~ 440') # Row 1
98
+ p.add('*~ 0.5') # Row 2
99
+ p.add('dac~') # Row 3
100
+ ```
101
+
102
+ **Grid layout** - organized columns:
103
+ ```python
104
+ from py2pd import Patcher, GridLayoutManager
105
+
106
+ grid = GridLayoutManager(columns=4, cell_width=80, cell_height=35)
107
+ p = Patcher('grid.pd', layout=grid)
108
+ ```
109
+
110
+ **Auto layout** - arrange by signal flow:
111
+ ```python
112
+ p = Patcher('patch.pd')
113
+ # Add nodes in any order...
114
+ p.auto_layout(margin=50, row_spacing=50, col_spacing=100)
115
+ ```
116
+
117
+ ### Saving and Export
118
+
119
+ ```python
120
+ p.save() # Save to filename from constructor
121
+ p.save('other.pd') # Save to specific file
122
+ p.save_svg('patch.svg') # Export visualization as SVG
123
+ svg_str = p.to_svg() # Get SVG as string
124
+ ```
125
+
126
+ ### Validation
127
+
128
+ ```python
129
+ p.validate_connections(check_cycles=True) # Raises on invalid connections
130
+ ```
131
+
132
+ ## AST API (Round-trip Parsing)
133
+
134
+ For modifying existing patches with immutable AST nodes:
135
+
136
+ ```python
137
+ from py2pd import parse_file, serialize
138
+
139
+ # Parse existing patch
140
+ ast = parse_file('input.pd')
141
+
142
+ # Modify the AST...
143
+
144
+ # Write back
145
+ with open('output.pd', 'w') as f:
146
+ f.write(serialize(ast))
147
+ ```
148
+
149
+ ### Converting Between APIs
150
+
151
+ You can convert between AST and Builder representations:
152
+
153
+ ```python
154
+ from py2pd import parse_file, to_builder, from_builder
155
+
156
+ # AST -> Builder: parse then edit with the more convenient API
157
+ ast = parse_file('input.pd')
158
+ patch = to_builder(ast)
159
+ patch.add('osc~ 880')
160
+ patch.save('output.pd')
161
+
162
+ # Builder -> AST: for analysis or transformation
163
+ ast = from_builder(patch)
164
+ ```
165
+
166
+ ### When to Use Each API
167
+
168
+ | Use Case | Recommended API |
169
+ |----------|-----------------|
170
+ | Creating patches from scratch | Builder |
171
+ | Modifying existing patches | Builder (via `to_builder()`) |
172
+ | Lossless round-trip of complex patches | AST |
173
+ | Building analysis/refactoring tools | AST |
174
+ | Batch search/replace across .pd files | AST |
175
+
176
+ For most workflows, parse to AST then convert to Builder for editing. Use the AST API directly when you need to preserve elements the Builder doesn't model (e.g., `coords`, comments) or need immutable transformations.
177
+
178
+ AST node types are available from the `py2pd.ast` module:
179
+
180
+ ```python
181
+ from py2pd.ast import PdPatch, PdObj, PdMsg, Position, transform, find_objects
182
+ ```
183
+
184
+ ## GUI Elements
185
+
186
+ | Method | Description |
187
+ |--------|-------------|
188
+ | `add_bang()` | Bang button |
189
+ | `add_toggle()` | On/off toggle |
190
+ | `add_numberbox()` | Editable number |
191
+ | `add_float()` | Float atom |
192
+ | `add_symbol()` | Symbol/text input |
193
+ | `add_hslider()` | Horizontal slider |
194
+ | `add_vslider()` | Vertical slider |
195
+ | `add_hradio()` | Horizontal radio buttons |
196
+ | `add_vradio()` | Vertical radio buttons |
197
+ | `add_canvas()` | Background/label area |
198
+ | `add_vu()` | VU meter |
199
+
200
+ ## Error Handling
201
+
202
+ ```python
203
+ from py2pd import (
204
+ ConnectionError, # Invalid connection arguments
205
+ NodeNotFoundError, # Node not in patch
206
+ InvalidConnectionError, # Bad inlet/outlet index
207
+ CycleWarning, # Feedback loop detected
208
+ )
209
+ ```
@@ -0,0 +1,62 @@
1
+ [project]
2
+ name = "py2pd"
3
+ version = "0.1.1"
4
+ description = "Roundtrip parsing and generation of pure-data patches from python"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ license = "MIT"
8
+ authors = [
9
+ { name = "Shakeeb Alireza", email = "shakfu@users.noreply.github.com" }
10
+ ]
11
+ keywords = ["puredata", "pd", "audio", "dsp", "music", "synthesis", "patching"]
12
+ classifiers = [
13
+ "Development Status :: 4 - Beta",
14
+ "Environment :: Console",
15
+ "Intended Audience :: Developers",
16
+ "Intended Audience :: End Users/Desktop",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.13",
21
+ "Programming Language :: Python :: Implementation :: CPython",
22
+ "Topic :: Multimedia :: Sound/Audio",
23
+ "Topic :: Multimedia :: Sound/Audio :: Sound Synthesis",
24
+ "Topic :: Software Development :: Code Generators",
25
+ "Topic :: Software Development :: Libraries :: Python Modules",
26
+ "Typing :: Typed",
27
+ ]
28
+
29
+ dependencies = []
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/shakfu/py2pd"
33
+ Repository = "https://github.com/shakfu/py2pd"
34
+ Issues = "https://github.com/shakfu/py2pd/issues"
35
+ Changelog = "https://github.com/shakfu/py2pd/blob/main/CHANGELOG.md"
36
+
37
+ [dependency-groups]
38
+ dev = [
39
+ "mypy>=1.19.1",
40
+ "pytest>=9.0.2",
41
+ "ruff>=0.14.13",
42
+ "twine>=6.2.0",
43
+ ]
44
+
45
+ [build-system]
46
+ requires = ["uv_build>=0.9.26,<0.10.0"]
47
+ build-backend = "uv_build"
48
+
49
+ [tool.pytest.ini_options]
50
+ testpaths = ["tests"]
51
+
52
+ [tool.mypy]
53
+ python_version = "3.13"
54
+ warn_return_any = true
55
+ warn_unused_configs = true
56
+
57
+ [tool.ruff]
58
+ target-version = "py313"
59
+ line-length = 100
60
+
61
+ [tool.ruff.lint]
62
+ select = ["E", "F", "I", "W"]
@@ -0,0 +1,86 @@
1
+ """
2
+ py2pd - Python to PureData
3
+ ==========================
4
+
5
+ Write PureData patches as Python programs.
6
+
7
+ Builder API example:
8
+ >>> from py2pd import Patcher
9
+ >>> p = Patcher('patch.pd')
10
+ >>> osc = p.add('osc~ 440')
11
+ >>> dac = p.add('dac~')
12
+ >>> p.link(osc, dac)
13
+ >>> p.link(osc, dac, inlet=1) # stereo
14
+ >>> p.save()
15
+
16
+ AST API example (round-trip):
17
+ >>> from py2pd import parse_file, serialize
18
+ >>> ast = parse_file('input.pd')
19
+ >>> # Modify the AST...
20
+ >>> with open('output.pd', 'w') as f:
21
+ ... f.write(serialize(ast))
22
+ """
23
+
24
+ # Builder API
25
+ from .api import (
26
+ COLUMN_WIDTH as COLUMN_WIDTH,
27
+ )
28
+ from .api import (
29
+ DEFAULT_MARGIN as DEFAULT_MARGIN,
30
+ )
31
+ from .api import (
32
+ # Layout constants
33
+ ROW_HEIGHT as ROW_HEIGHT,
34
+ )
35
+ from .api import (
36
+ SUBPATCH_CANVAS_HEIGHT as SUBPATCH_CANVAS_HEIGHT,
37
+ )
38
+ from .api import (
39
+ SUBPATCH_CANVAS_WIDTH as SUBPATCH_CANVAS_WIDTH,
40
+ )
41
+ from .api import (
42
+ ConnectionError as ConnectionError,
43
+ )
44
+ from .api import (
45
+ CycleWarning as CycleWarning,
46
+ )
47
+ from .api import (
48
+ GridLayoutManager as GridLayoutManager,
49
+ )
50
+ from .api import (
51
+ InvalidConnectionError as InvalidConnectionError,
52
+ )
53
+ from .api import (
54
+ LayoutManager as LayoutManager,
55
+ )
56
+ from .api import (
57
+ NodeNotFoundError as NodeNotFoundError,
58
+ )
59
+ from .api import (
60
+ Patcher as Patcher,
61
+ )
62
+ from .ast import (
63
+ ParseError as ParseError,
64
+ )
65
+ from .ast import (
66
+ from_builder as from_builder,
67
+ )
68
+
69
+ # AST API (node types available via: from py2pd.ast import PdPatch, PdObj, ...)
70
+ from .ast import (
71
+ parse as parse,
72
+ )
73
+ from .ast import (
74
+ parse_file as parse_file,
75
+ )
76
+ from .ast import (
77
+ serialize as serialize,
78
+ )
79
+ from .ast import (
80
+ serialize_to_file as serialize_to_file,
81
+ )
82
+ from .ast import (
83
+ to_builder as to_builder,
84
+ )
85
+
86
+ __version__ = "0.1.1"