dsp-graph 0.1.5__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.
@@ -0,0 +1,239 @@
1
+ Metadata-Version: 2.3
2
+ Name: dsp-graph
3
+ Version: 0.1.5
4
+ Summary: A Python DSL for defining DSP signal graphs with C++ compilation and optimization
5
+ Keywords: dsp,audio,signal-processing,codegen,c++,pydantic,graph
6
+ Author: Shakeeb Alireza
7
+ Author-email: Shakeeb Alireza <shakfu@users.noreply.github.com>
8
+ License: MIT
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Other Audience
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: MacOS
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Topic :: Multimedia :: Sound/Audio
23
+ Classifier: Topic :: Software Development :: Code Generators
24
+ Requires-Dist: pydantic>=2.0
25
+ Requires-Python: >=3.10
26
+ Project-URL: Homepage, https://github.com/shakfu/dsp-graph
27
+ Project-URL: Repository, https://github.com/shakfu/dsp-graph
28
+ Project-URL: Changelog, https://github.com/shakfu/dsp-graph/blob/main/CHANGELOG.md
29
+ Project-URL: Issues, https://github.com/shakfu/dsp-graph/issues
30
+ Description-Content-Type: text/markdown
31
+
32
+ # dsp-graph
33
+
34
+ A Python DSL for defining DSP signal graphs, compiling them to standalone C++, and optimizing the result.
35
+
36
+ Define audio processing graphs using Pydantic models, validate them, compile to C++, and serialize to/from JSON. Zero runtime dependencies beyond Pydantic.
37
+
38
+ ## Install
39
+
40
+ ```bash
41
+ pip install dsp-graph
42
+ ```
43
+
44
+ For development:
45
+
46
+ ```bash
47
+ git clone https://github.com/shakfu/dsp-graph.git
48
+ cd dsp-graph
49
+ make install-dev
50
+ ```
51
+
52
+ ## Quick Start
53
+
54
+ ```python
55
+ from dsp_graph import (
56
+ AudioInput, AudioOutput, BinOp, Graph, History, Param,
57
+ compile_graph, validate_graph,
58
+ )
59
+
60
+ graph = Graph(
61
+ name="onepole",
62
+ inputs=[AudioInput(id="in1")],
63
+ outputs=[AudioOutput(id="out1", source="result")],
64
+ params=[Param(name="coeff", min=0.0, max=0.999, default=0.5)],
65
+ nodes=[
66
+ BinOp(id="inv_coeff", op="sub", a=1.0, b="coeff"),
67
+ BinOp(id="dry", op="mul", a="in1", b="inv_coeff"),
68
+ History(id="prev", init=0.0, input="result"),
69
+ BinOp(id="wet", op="mul", a="prev", b="coeff"),
70
+ BinOp(id="result", op="add", a="dry", b="wet"),
71
+ ],
72
+ )
73
+
74
+ errors = validate_graph(graph)
75
+ assert errors == []
76
+
77
+ code = compile_graph(graph) # standalone C++ string
78
+ print(graph.model_dump_json(indent=2))
79
+ ```
80
+
81
+ ## Node Types (34)
82
+
83
+ ### Arithmetic / Math
84
+
85
+ | Node | `op` | Fields | Purpose |
86
+ |------|------|--------|---------|
87
+ | `BinOp` | `add`, `sub`, `mul`, `div`, `min`, `max`, `mod`, `pow` | `a`, `b` | Binary arithmetic |
88
+ | `UnaryOp` | `sin`, `cos`, `tanh`, `exp`, `log`, `abs`, `sqrt`, `neg`, `floor`, `ceil`, `round`, `sign`, `atan`, `asin`, `acos` | `a` | Unary math functions |
89
+ | `Clamp` | `clamp` | `a`, `lo`, `hi` | Saturate to `[lo, hi]` |
90
+ | `Constant` | `constant` | `value` | Literal float value |
91
+ | `Compare` | `gt`, `lt`, `gte`, `lte`, `eq` | `a`, `b` | Comparison (returns 0.0 or 1.0) |
92
+ | `Select` | `select` | `cond`, `a`, `b` | Conditional: `a` if `cond > 0`, else `b` |
93
+ | `Wrap` | `wrap` | `a`, `lo`, `hi` | Wrap value into range |
94
+ | `Fold` | `fold` | `a`, `lo`, `hi` | Fold (reflect) value into range |
95
+ | `Mix` | `mix` | `a`, `b`, `t` | Linear interpolation: `a + (b - a) * t` |
96
+
97
+ ### Delay
98
+
99
+ | Node | `op` | Fields | Purpose |
100
+ |------|------|--------|---------|
101
+ | `DelayLine` | `delay` | `max_samples` | Circular buffer declaration |
102
+ | `DelayRead` | `delay_read` | `delay`, `tap`, `interp` | Read from delay line (none/linear/cubic) |
103
+ | `DelayWrite` | `delay_write` | `delay`, `value` | Write to delay line |
104
+ | `History` | `history` | `input`, `init` | Single-sample delay (z^-1 feedback) |
105
+
106
+ ### Buffer / Table
107
+
108
+ | Node | `op` | Fields | Purpose |
109
+ |------|------|--------|---------|
110
+ | `Buffer` | `buffer` | `size` | Random-access data buffer |
111
+ | `BufRead` | `buf_read` | `buffer`, `index`, `interp` | Read from buffer (none/linear/cubic, clamped) |
112
+ | `BufWrite` | `buf_write` | `buffer`, `index`, `value` | Write to buffer at index |
113
+ | `BufSize` | `buf_size` | `buffer` | Returns buffer length as float |
114
+
115
+ ### Filters
116
+
117
+ | Node | `op` | Fields | Purpose |
118
+ |------|------|--------|---------|
119
+ | `Biquad` | `biquad` | `a`, `b0`, `b1`, `b2`, `a1`, `a2` | Generic biquad (user supplies coefficients) |
120
+ | `SVF` | `svf` | `a`, `freq`, `q`, `mode` | State-variable filter (lp/hp/bp/notch) |
121
+ | `OnePole` | `onepole` | `a`, `coeff` | One-pole lowpass |
122
+ | `DCBlock` | `dcblock` | `a` | DC blocking filter |
123
+ | `Allpass` | `allpass` | `a`, `coeff` | First-order allpass |
124
+
125
+ ### Oscillators / Sources
126
+
127
+ | Node | `op` | Fields | Purpose |
128
+ |------|------|--------|---------|
129
+ | `Phasor` | `phasor` | `freq` | Ramp oscillator 0..1 |
130
+ | `SinOsc` | `sinosc` | `freq` | Sine oscillator |
131
+ | `TriOsc` | `triosc` | `freq` | Triangle wave |
132
+ | `SawOsc` | `sawosc` | `freq` | Bipolar saw (-1..1) |
133
+ | `PulseOsc` | `pulseosc` | `freq`, `width` | Pulse/square with variable duty cycle |
134
+ | `Noise` | `noise` | -- | White noise source |
135
+
136
+ ### State / Timing
137
+
138
+ | Node | `op` | Fields | Purpose |
139
+ |------|------|--------|---------|
140
+ | `Delta` | `delta` | `a` | Sample-to-sample difference |
141
+ | `Change` | `change` | `a` | 1.0 if value changed, else 0.0 |
142
+ | `SampleHold` | `sample_hold` | `a`, `trig` | Latch on any zero crossing |
143
+ | `Latch` | `latch` | `a`, `trig` | Latch on rising edge only |
144
+ | `Accum` | `accum` | `incr`, `reset` | Running sum, resets when `reset > 0` |
145
+ | `Counter` | `counter` | `trig`, `max` | Integer counter, wraps at max |
146
+
147
+ ## C++ Compilation
148
+
149
+ `compile_graph()` generates a single self-contained `.cpp` file with:
150
+
151
+ - A state struct (`{Name}State`)
152
+ - `create(sr)` / `destroy(self)` / `reset(self)` lifecycle
153
+ - `perform(self, ins, outs, n)` sample-processing loop
154
+ - Param introspection: `num_params`, `param_name`, `param_min`, `param_max`, `set_param`, `get_param`
155
+ - Buffer introspection: `num_buffers`, `buffer_name`, `buffer_size`, `get_buffer`, `set_buffer`
156
+
157
+ ```python
158
+ from dsp_graph import compile_graph, compile_graph_to_file
159
+
160
+ code = compile_graph(graph) # returns C++ string
161
+ path = compile_graph_to_file(graph, "build/") # writes build/{name}.cpp
162
+ ```
163
+
164
+ ## gen-dsp Integration
165
+
166
+ dsp-graph graphs can be compiled into buildable audio plugin projects via [gen-dsp](https://github.com/shakfu/gen-dsp), which supports 11 platforms: ChucK, CLAP, AudioUnit, VST3, LV2, SuperCollider, VCV Rack, Daisy, and more.
167
+
168
+ `compile_for_gen_dsp()` generates the three files needed to drop into any gen-dsp platform backend:
169
+
170
+ ```python
171
+ from dsp_graph import compile_for_gen_dsp
172
+
173
+ # Generates: test_synth.cpp, _ext_chuck.cpp, manifest.json
174
+ compile_for_gen_dsp(graph, "build/", platform="chuck")
175
+ ```
176
+
177
+ The adapter replaces gen-dsp's genlib-side code while reusing its platform-side code unchanged. The generated `manifest.json` is compatible with `gen_dsp.core.manifest.Manifest`.
178
+
179
+ For a fully assembled project (requires gen-dsp installed):
180
+
181
+ ```python
182
+ from dsp_graph.gen_dsp_adapter import assemble_project
183
+
184
+ # Copies platform templates + generates adapter + manifest
185
+ assemble_project(graph, "build/chuck_project", platform="chuck")
186
+ ```
187
+
188
+ ## Optimization
189
+
190
+ ```python
191
+ from dsp_graph import optimize_graph, constant_fold, eliminate_cse, eliminate_dead_nodes
192
+
193
+ optimized = optimize_graph(graph) # constant folding + CSE + dead node elimination
194
+ ```
195
+
196
+ - **Constant folding**: pure nodes with all-constant inputs are replaced by `Constant` nodes
197
+ - **Common subexpression elimination**: duplicate pure nodes with identical inputs are merged
198
+ - **Dead node elimination**: nodes not reachable from any output are removed (respects side-effecting writers for delay lines and buffers)
199
+ - **Loop-invariant code motion**: param-only expressions are hoisted before the sample loop
200
+ - **SIMD hints**: `__restrict` on I/O pointers; vectorization pragmas for pure-only graphs
201
+
202
+ ## Validation
203
+
204
+ `validate_graph()` checks:
205
+
206
+ 1. Unique node IDs (no collisions with inputs or params)
207
+ 2. All string references resolve to existing IDs
208
+ 3. Output sources reference existing nodes
209
+ 4. DelayRead/DelayWrite reference existing DelayLine nodes
210
+ 5. BufRead/BufWrite/BufSize reference existing Buffer nodes
211
+ 6. No pure cycles (cycles must pass through History or delay)
212
+
213
+ ## Visualization
214
+
215
+ ```python
216
+ from dsp_graph import graph_to_dot, graph_to_dot_file
217
+
218
+ dot_str = graph_to_dot(graph) # DOT string
219
+ dot_path = graph_to_dot_file(graph, "build/") # writes .dot, renders .pdf if `dot` is on PATH
220
+ ```
221
+
222
+ ## Examples
223
+
224
+ See `examples/` for complete working graphs:
225
+
226
+ - `stereo_gain.py` -- stateless stereo gain
227
+ - `onepole.py` -- one-pole lowpass with History feedback
228
+ - `fbdelay.py` -- feedback delay with dry/wet mix
229
+ - `wavetable.py` -- wavetable oscillator using Buffer + Phasor + BufRead
230
+
231
+ ## Development
232
+
233
+ ```bash
234
+ make install-dev # install with dev deps
235
+ make test # run tests
236
+ make lint # ruff check
237
+ make typecheck # mypy --strict
238
+ make qa # all of the above
239
+ ```
@@ -0,0 +1,208 @@
1
+ # dsp-graph
2
+
3
+ A Python DSL for defining DSP signal graphs, compiling them to standalone C++, and optimizing the result.
4
+
5
+ Define audio processing graphs using Pydantic models, validate them, compile to C++, and serialize to/from JSON. Zero runtime dependencies beyond Pydantic.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install dsp-graph
11
+ ```
12
+
13
+ For development:
14
+
15
+ ```bash
16
+ git clone https://github.com/shakfu/dsp-graph.git
17
+ cd dsp-graph
18
+ make install-dev
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ```python
24
+ from dsp_graph import (
25
+ AudioInput, AudioOutput, BinOp, Graph, History, Param,
26
+ compile_graph, validate_graph,
27
+ )
28
+
29
+ graph = Graph(
30
+ name="onepole",
31
+ inputs=[AudioInput(id="in1")],
32
+ outputs=[AudioOutput(id="out1", source="result")],
33
+ params=[Param(name="coeff", min=0.0, max=0.999, default=0.5)],
34
+ nodes=[
35
+ BinOp(id="inv_coeff", op="sub", a=1.0, b="coeff"),
36
+ BinOp(id="dry", op="mul", a="in1", b="inv_coeff"),
37
+ History(id="prev", init=0.0, input="result"),
38
+ BinOp(id="wet", op="mul", a="prev", b="coeff"),
39
+ BinOp(id="result", op="add", a="dry", b="wet"),
40
+ ],
41
+ )
42
+
43
+ errors = validate_graph(graph)
44
+ assert errors == []
45
+
46
+ code = compile_graph(graph) # standalone C++ string
47
+ print(graph.model_dump_json(indent=2))
48
+ ```
49
+
50
+ ## Node Types (34)
51
+
52
+ ### Arithmetic / Math
53
+
54
+ | Node | `op` | Fields | Purpose |
55
+ |------|------|--------|---------|
56
+ | `BinOp` | `add`, `sub`, `mul`, `div`, `min`, `max`, `mod`, `pow` | `a`, `b` | Binary arithmetic |
57
+ | `UnaryOp` | `sin`, `cos`, `tanh`, `exp`, `log`, `abs`, `sqrt`, `neg`, `floor`, `ceil`, `round`, `sign`, `atan`, `asin`, `acos` | `a` | Unary math functions |
58
+ | `Clamp` | `clamp` | `a`, `lo`, `hi` | Saturate to `[lo, hi]` |
59
+ | `Constant` | `constant` | `value` | Literal float value |
60
+ | `Compare` | `gt`, `lt`, `gte`, `lte`, `eq` | `a`, `b` | Comparison (returns 0.0 or 1.0) |
61
+ | `Select` | `select` | `cond`, `a`, `b` | Conditional: `a` if `cond > 0`, else `b` |
62
+ | `Wrap` | `wrap` | `a`, `lo`, `hi` | Wrap value into range |
63
+ | `Fold` | `fold` | `a`, `lo`, `hi` | Fold (reflect) value into range |
64
+ | `Mix` | `mix` | `a`, `b`, `t` | Linear interpolation: `a + (b - a) * t` |
65
+
66
+ ### Delay
67
+
68
+ | Node | `op` | Fields | Purpose |
69
+ |------|------|--------|---------|
70
+ | `DelayLine` | `delay` | `max_samples` | Circular buffer declaration |
71
+ | `DelayRead` | `delay_read` | `delay`, `tap`, `interp` | Read from delay line (none/linear/cubic) |
72
+ | `DelayWrite` | `delay_write` | `delay`, `value` | Write to delay line |
73
+ | `History` | `history` | `input`, `init` | Single-sample delay (z^-1 feedback) |
74
+
75
+ ### Buffer / Table
76
+
77
+ | Node | `op` | Fields | Purpose |
78
+ |------|------|--------|---------|
79
+ | `Buffer` | `buffer` | `size` | Random-access data buffer |
80
+ | `BufRead` | `buf_read` | `buffer`, `index`, `interp` | Read from buffer (none/linear/cubic, clamped) |
81
+ | `BufWrite` | `buf_write` | `buffer`, `index`, `value` | Write to buffer at index |
82
+ | `BufSize` | `buf_size` | `buffer` | Returns buffer length as float |
83
+
84
+ ### Filters
85
+
86
+ | Node | `op` | Fields | Purpose |
87
+ |------|------|--------|---------|
88
+ | `Biquad` | `biquad` | `a`, `b0`, `b1`, `b2`, `a1`, `a2` | Generic biquad (user supplies coefficients) |
89
+ | `SVF` | `svf` | `a`, `freq`, `q`, `mode` | State-variable filter (lp/hp/bp/notch) |
90
+ | `OnePole` | `onepole` | `a`, `coeff` | One-pole lowpass |
91
+ | `DCBlock` | `dcblock` | `a` | DC blocking filter |
92
+ | `Allpass` | `allpass` | `a`, `coeff` | First-order allpass |
93
+
94
+ ### Oscillators / Sources
95
+
96
+ | Node | `op` | Fields | Purpose |
97
+ |------|------|--------|---------|
98
+ | `Phasor` | `phasor` | `freq` | Ramp oscillator 0..1 |
99
+ | `SinOsc` | `sinosc` | `freq` | Sine oscillator |
100
+ | `TriOsc` | `triosc` | `freq` | Triangle wave |
101
+ | `SawOsc` | `sawosc` | `freq` | Bipolar saw (-1..1) |
102
+ | `PulseOsc` | `pulseosc` | `freq`, `width` | Pulse/square with variable duty cycle |
103
+ | `Noise` | `noise` | -- | White noise source |
104
+
105
+ ### State / Timing
106
+
107
+ | Node | `op` | Fields | Purpose |
108
+ |------|------|--------|---------|
109
+ | `Delta` | `delta` | `a` | Sample-to-sample difference |
110
+ | `Change` | `change` | `a` | 1.0 if value changed, else 0.0 |
111
+ | `SampleHold` | `sample_hold` | `a`, `trig` | Latch on any zero crossing |
112
+ | `Latch` | `latch` | `a`, `trig` | Latch on rising edge only |
113
+ | `Accum` | `accum` | `incr`, `reset` | Running sum, resets when `reset > 0` |
114
+ | `Counter` | `counter` | `trig`, `max` | Integer counter, wraps at max |
115
+
116
+ ## C++ Compilation
117
+
118
+ `compile_graph()` generates a single self-contained `.cpp` file with:
119
+
120
+ - A state struct (`{Name}State`)
121
+ - `create(sr)` / `destroy(self)` / `reset(self)` lifecycle
122
+ - `perform(self, ins, outs, n)` sample-processing loop
123
+ - Param introspection: `num_params`, `param_name`, `param_min`, `param_max`, `set_param`, `get_param`
124
+ - Buffer introspection: `num_buffers`, `buffer_name`, `buffer_size`, `get_buffer`, `set_buffer`
125
+
126
+ ```python
127
+ from dsp_graph import compile_graph, compile_graph_to_file
128
+
129
+ code = compile_graph(graph) # returns C++ string
130
+ path = compile_graph_to_file(graph, "build/") # writes build/{name}.cpp
131
+ ```
132
+
133
+ ## gen-dsp Integration
134
+
135
+ dsp-graph graphs can be compiled into buildable audio plugin projects via [gen-dsp](https://github.com/shakfu/gen-dsp), which supports 11 platforms: ChucK, CLAP, AudioUnit, VST3, LV2, SuperCollider, VCV Rack, Daisy, and more.
136
+
137
+ `compile_for_gen_dsp()` generates the three files needed to drop into any gen-dsp platform backend:
138
+
139
+ ```python
140
+ from dsp_graph import compile_for_gen_dsp
141
+
142
+ # Generates: test_synth.cpp, _ext_chuck.cpp, manifest.json
143
+ compile_for_gen_dsp(graph, "build/", platform="chuck")
144
+ ```
145
+
146
+ The adapter replaces gen-dsp's genlib-side code while reusing its platform-side code unchanged. The generated `manifest.json` is compatible with `gen_dsp.core.manifest.Manifest`.
147
+
148
+ For a fully assembled project (requires gen-dsp installed):
149
+
150
+ ```python
151
+ from dsp_graph.gen_dsp_adapter import assemble_project
152
+
153
+ # Copies platform templates + generates adapter + manifest
154
+ assemble_project(graph, "build/chuck_project", platform="chuck")
155
+ ```
156
+
157
+ ## Optimization
158
+
159
+ ```python
160
+ from dsp_graph import optimize_graph, constant_fold, eliminate_cse, eliminate_dead_nodes
161
+
162
+ optimized = optimize_graph(graph) # constant folding + CSE + dead node elimination
163
+ ```
164
+
165
+ - **Constant folding**: pure nodes with all-constant inputs are replaced by `Constant` nodes
166
+ - **Common subexpression elimination**: duplicate pure nodes with identical inputs are merged
167
+ - **Dead node elimination**: nodes not reachable from any output are removed (respects side-effecting writers for delay lines and buffers)
168
+ - **Loop-invariant code motion**: param-only expressions are hoisted before the sample loop
169
+ - **SIMD hints**: `__restrict` on I/O pointers; vectorization pragmas for pure-only graphs
170
+
171
+ ## Validation
172
+
173
+ `validate_graph()` checks:
174
+
175
+ 1. Unique node IDs (no collisions with inputs or params)
176
+ 2. All string references resolve to existing IDs
177
+ 3. Output sources reference existing nodes
178
+ 4. DelayRead/DelayWrite reference existing DelayLine nodes
179
+ 5. BufRead/BufWrite/BufSize reference existing Buffer nodes
180
+ 6. No pure cycles (cycles must pass through History or delay)
181
+
182
+ ## Visualization
183
+
184
+ ```python
185
+ from dsp_graph import graph_to_dot, graph_to_dot_file
186
+
187
+ dot_str = graph_to_dot(graph) # DOT string
188
+ dot_path = graph_to_dot_file(graph, "build/") # writes .dot, renders .pdf if `dot` is on PATH
189
+ ```
190
+
191
+ ## Examples
192
+
193
+ See `examples/` for complete working graphs:
194
+
195
+ - `stereo_gain.py` -- stateless stereo gain
196
+ - `onepole.py` -- one-pole lowpass with History feedback
197
+ - `fbdelay.py` -- feedback delay with dry/wet mix
198
+ - `wavetable.py` -- wavetable oscillator using Buffer + Phasor + BufRead
199
+
200
+ ## Development
201
+
202
+ ```bash
203
+ make install-dev # install with dev deps
204
+ make test # run tests
205
+ make lint # ruff check
206
+ make typecheck # mypy --strict
207
+ make qa # all of the above
208
+ ```
@@ -0,0 +1,68 @@
1
+ [project]
2
+ name = "dsp-graph"
3
+ version = "0.1.5"
4
+ description = "A Python DSL for defining DSP signal graphs with C++ compilation and optimization"
5
+ readme = "README.md"
6
+ license = {text = "MIT"}
7
+ authors = [
8
+ { name = "Shakeeb Alireza", email = "shakfu@users.noreply.github.com" }
9
+ ]
10
+ keywords = ["dsp", "audio", "signal-processing", "codegen", "c++", "pydantic", "graph"]
11
+ classifiers = [
12
+ "Development Status :: 3 - Alpha",
13
+ "Environment :: Console",
14
+ "Intended Audience :: Developers",
15
+ "Intended Audience :: Other Audience",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: MacOS",
18
+ "Operating System :: POSIX :: Linux",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Programming Language :: Python :: 3.14",
25
+ "Topic :: Multimedia :: Sound/Audio",
26
+ "Topic :: Software Development :: Code Generators",
27
+ ]
28
+ requires-python = ">=3.10"
29
+ dependencies = [
30
+ "pydantic>=2.0",
31
+ ]
32
+
33
+ [dependency-groups]
34
+ dev = [
35
+ "gen-dsp>=0.1.6",
36
+ "mypy>=1.19.1",
37
+ "pytest>=9.0.2",
38
+ "pytest-cov>=7.0.0",
39
+ "ruff>=0.15.0",
40
+ "twine>=6.2.0",
41
+ ]
42
+
43
+ [project.urls]
44
+ Homepage = "https://github.com/shakfu/dsp-graph"
45
+ Repository = "https://github.com/shakfu/dsp-graph"
46
+ Changelog = "https://github.com/shakfu/dsp-graph/blob/main/CHANGELOG.md"
47
+ Issues = "https://github.com/shakfu/dsp-graph/issues"
48
+
49
+ [build-system]
50
+ requires = ["uv_build>=0.10.2,<0.11.0"]
51
+ build-backend = "uv_build"
52
+
53
+ [tool.pytest.ini_options]
54
+ testpaths = ["tests"]
55
+ addopts = "--cov=dsp_graph --cov-report term-missing:skip-covered"
56
+
57
+ [tool.mypy]
58
+ strict = true
59
+ packages = ["dsp_graph"]
60
+ mypy_path = "src"
61
+ plugins = ["pydantic.mypy"]
62
+
63
+ [tool.ruff]
64
+ src = ["src"]
65
+ line-length = 99
66
+
67
+ [tool.ruff.lint]
68
+ select = ["E", "F", "I", "W"]
@@ -0,0 +1,118 @@
1
+ """Define DSP signal graphs as Pydantic models, compile to C++, and optimize.
2
+
3
+ Provides 34 node types (arithmetic, filters, oscillators, delays, buffers,
4
+ state/timing), graph validation, topological sort, Graphviz visualization,
5
+ and a multi-pass optimizing compiler targeting standalone C++. Optional
6
+ gen-dsp integration generates adapter code for 10+ audio plugin platforms.
7
+ """
8
+
9
+ from dsp_graph.compile import compile_graph, compile_graph_to_file
10
+ from dsp_graph.gen_dsp_adapter import (
11
+ compile_for_gen_dsp,
12
+ generate_adapter_cpp,
13
+ generate_manifest,
14
+ )
15
+ from dsp_graph.models import (
16
+ SVF,
17
+ Accum,
18
+ Allpass,
19
+ AudioInput,
20
+ AudioOutput,
21
+ BinOp,
22
+ Biquad,
23
+ Buffer,
24
+ BufRead,
25
+ BufSize,
26
+ BufWrite,
27
+ Change,
28
+ Clamp,
29
+ Compare,
30
+ Constant,
31
+ Counter,
32
+ DCBlock,
33
+ DelayLine,
34
+ DelayRead,
35
+ DelayWrite,
36
+ Delta,
37
+ Fold,
38
+ Graph,
39
+ History,
40
+ Latch,
41
+ Mix,
42
+ Node,
43
+ Noise,
44
+ OnePole,
45
+ Param,
46
+ Phasor,
47
+ PulseOsc,
48
+ Ref,
49
+ SampleHold,
50
+ SawOsc,
51
+ Select,
52
+ SinOsc,
53
+ TriOsc,
54
+ UnaryOp,
55
+ Wrap,
56
+ )
57
+ from dsp_graph.optimize import constant_fold, eliminate_cse, eliminate_dead_nodes, optimize_graph
58
+ from dsp_graph.toposort import toposort
59
+ from dsp_graph.validate import validate_graph
60
+ from dsp_graph.visualize import graph_to_dot, graph_to_dot_file
61
+
62
+ __version__ = "0.1.5"
63
+
64
+ __all__ = [
65
+ "Accum",
66
+ "Allpass",
67
+ "AudioInput",
68
+ "AudioOutput",
69
+ "BinOp",
70
+ "Biquad",
71
+ "BufRead",
72
+ "BufSize",
73
+ "BufWrite",
74
+ "Buffer",
75
+ "Change",
76
+ "Clamp",
77
+ "Compare",
78
+ "Constant",
79
+ "Counter",
80
+ "DCBlock",
81
+ "DelayLine",
82
+ "DelayRead",
83
+ "DelayWrite",
84
+ "Delta",
85
+ "Fold",
86
+ "Graph",
87
+ "History",
88
+ "Latch",
89
+ "Mix",
90
+ "Node",
91
+ "Noise",
92
+ "OnePole",
93
+ "Param",
94
+ "Phasor",
95
+ "PulseOsc",
96
+ "Ref",
97
+ "SVF",
98
+ "SampleHold",
99
+ "SawOsc",
100
+ "Select",
101
+ "SinOsc",
102
+ "TriOsc",
103
+ "UnaryOp",
104
+ "Wrap",
105
+ "compile_for_gen_dsp",
106
+ "compile_graph",
107
+ "compile_graph_to_file",
108
+ "constant_fold",
109
+ "eliminate_cse",
110
+ "eliminate_dead_nodes",
111
+ "generate_adapter_cpp",
112
+ "generate_manifest",
113
+ "graph_to_dot",
114
+ "graph_to_dot_file",
115
+ "optimize_graph",
116
+ "toposort",
117
+ "validate_graph",
118
+ ]
@@ -0,0 +1,37 @@
1
+ """Shared dependency helpers for graph analysis."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections import defaultdict
6
+
7
+ from dsp_graph.models import Graph, History
8
+
9
+
10
+ def is_feedback_edge(node: object, field_name: str) -> bool:
11
+ """Return True if a field on this node is a feedback edge (not a data dependency)."""
12
+ # History.input is written at end of sample -- reads previous value
13
+ if isinstance(node, History) and field_name == "input":
14
+ return True
15
+ # DelayRead reads from the delay line written by DelayWrite -- implicit feedback
16
+ # DelayWrite.value IS a data dependency (need the value this sample)
17
+ # But DelayRead.delay and DelayWrite.delay reference a DelayLine, not a data flow
18
+ return False
19
+
20
+
21
+ def build_forward_deps(graph: Graph) -> dict[str, set[str]]:
22
+ """Build forward dependency map: {node_id: set of node_ids it depends on}.
23
+
24
+ Excludes feedback edges (History.input) and non-node references
25
+ (audio inputs, param names).
26
+ """
27
+ node_ids = {node.id for node in graph.nodes}
28
+ deps: dict[str, set[str]] = defaultdict(set)
29
+ for node in graph.nodes:
30
+ nid = node.id
31
+ for field_name, value in node.__dict__.items():
32
+ if field_name in ("id", "op"):
33
+ continue
34
+ if isinstance(value, str) and value in node_ids:
35
+ if not is_feedback_edge(node, field_name):
36
+ deps[nid].add(value)
37
+ return deps