multiafx 0.1.0__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.
multiafx-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Barry Cheng
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,266 @@
1
+ Metadata-Version: 2.4
2
+ Name: multiafx
3
+ Version: 0.1.0
4
+ Summary: Multi-library audio effects registry with a unified chain-application API
5
+ Author-email: Barry Cheng <im31132@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/barry-mir/multiafx
8
+ Project-URL: Repository, https://github.com/barry-mir/multiafx
9
+ Project-URL: Issues, https://github.com/barry-mir/multiafx/issues
10
+ Keywords: audio,dsp,effects,signal-processing,augmentation
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Multimedia :: Sound/Audio
20
+ Classifier: Topic :: Scientific/Engineering
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: numpy<3,>=1.24
25
+ Requires-Dist: scipy>=1.10
26
+ Requires-Dist: pyyaml>=6.0
27
+ Requires-Dist: audiomentations>=0.43.1
28
+ Requires-Dist: sox>=1.4
29
+ Requires-Dist: torch>=2.0
30
+ Requires-Dist: torchaudio>=2.0
31
+ Requires-Dist: librosa>=0.10
32
+ Requires-Dist: pyloudnorm>=0.1.1
33
+ Requires-Dist: loudness>=0.2
34
+ Provides-Extra: dev
35
+ Requires-Dist: pytest>=7.0; extra == "dev"
36
+ Requires-Dist: pytest-cov; extra == "dev"
37
+ Requires-Dist: soundfile>=0.12; extra == "dev"
38
+ Requires-Dist: build>=1.0; extra == "dev"
39
+ Requires-Dist: twine>=4.0; extra == "dev"
40
+ Dynamic: license-file
41
+
42
+ <p align="center">
43
+ <img src="https://raw.githubusercontent.com/barry-mir/multiafx/main/assets/icon.png" alt="MultiAFX" width="200">
44
+ </p>
45
+
46
+ Unified registry of **85 audio effects** across **7 libraries** (audiomentations, sox, torchaudio, scipy, librosa, pyloudnorm, numpy), with a clean chain-application API inspired by [pedalboard](https://github.com/spotify/pedalboard).
47
+
48
+ - One call signature for every effect: `(audio, sr, **params) -> np.ndarray`
49
+ - JSON / YAML / pickle serialization of effect chains
50
+ - Pure data format: chains are lists of dicts, portable across languages
51
+ - Parameter ranges attached to every effect for random chain generation
52
+
53
+ ---
54
+
55
+ ## Install
56
+
57
+ ```bash
58
+ pip install multiafx
59
+ ```
60
+
61
+ Requires Python ≥ 3.10.
62
+
63
+ ---
64
+
65
+ ## Quickstart
66
+
67
+ ```python
68
+ import multiafx
69
+ import soundfile as sf
70
+
71
+ # Build a chain inline
72
+ chain = multiafx.FXChain([
73
+ {"effect": "sox_highpass", "params": {"frequency": 80.0, "width_q": 0.707}},
74
+ {"effect": "sox_compand", "params": {"attack_time": 0.005,
75
+ "decay_time": 0.1,
76
+ "soft_knee_db": 6.0}},
77
+ {"effect": "sox_reverb", "params": {"reverberance": 40.0,
78
+ "high_freq_damping": 50.0,
79
+ "room_scale": 60.0,
80
+ "stereo_depth": 80.0,
81
+ "pre_delay": 20.0,
82
+ "wet_gain": -6.0}},
83
+ ])
84
+
85
+ # Load audio as (channels, samples) float32
86
+ audio, sr = sf.read("input.wav", always_2d=True)
87
+ audio = audio.T.astype("float32")
88
+
89
+ # Apply — pedalboard style
90
+ processed = chain(audio, sr)
91
+
92
+ sf.write("output.wav", processed.T, sr)
93
+ ```
94
+
95
+ ---
96
+
97
+ ## Loading chains from files
98
+
99
+ ```python
100
+ chain = multiafx.FXChain.load("preset.json") # auto-detect by extension
101
+ chain = multiafx.FXChain.load("preset.yaml")
102
+ chain = multiafx.FXChain.load("preset.pkl")
103
+
104
+ # or explicitly
105
+ chain = multiafx.FXChain.from_json("preset.json")
106
+ chain = multiafx.FXChain.from_yaml("preset.yaml")
107
+ chain = multiafx.FXChain.from_pickle("preset.pkl")
108
+ ```
109
+
110
+ **JSON format** (`preset.json`):
111
+
112
+ ```json
113
+ [
114
+ {"effect": "sox_highpass", "params": {"frequency": 80.0, "width_q": 0.707}},
115
+ {"effect": "sox_compand", "params": {"attack_time": 0.005,
116
+ "decay_time": 0.1,
117
+ "soft_knee_db": 6.0}}
118
+ ]
119
+ ```
120
+
121
+ **YAML format** (`preset.yaml`):
122
+
123
+ ```yaml
124
+ - effect: sox_highpass
125
+ params: {frequency: 80.0, width_q: 0.707}
126
+ - effect: sox_compand
127
+ params:
128
+ attack_time: 0.005
129
+ decay_time: 0.1
130
+ soft_knee_db: 6.0
131
+ ```
132
+
133
+ Saving is symmetric: `chain.to_json(path)`, `chain.to_yaml(path)`, `chain.to_pickle(path)`.
134
+
135
+ ---
136
+
137
+ ## Building chains programmatically
138
+
139
+ ```python
140
+ chain = multiafx.FXChain()
141
+ chain.add("sox_highpass", frequency=100.0, width_q=0.707)
142
+ chain.add("sox_compand", attack_time=0.005, decay_time=0.1, soft_knee_db=4.0)
143
+ chain.add("sox_reverb", reverberance=30.0, high_freq_damping=50.0, room_scale=40.0,
144
+ stereo_depth=100.0, pre_delay=15.0, wet_gain=-8.0)
145
+
146
+ # List-like mutation
147
+ chain.append({"effect": "sox_gain", "params": {"gain_db": 2.0}})
148
+ chain.insert(0, {"effect": "sox_highpass", "params": {"frequency": 40.0, "width_q": 0.7}})
149
+ del chain[-1]
150
+ chain.pop()
151
+ len(chain)
152
+ for step in chain: ...
153
+ ```
154
+
155
+ ### Default parameters
156
+
157
+ Every effect has sensible defaults, so you can omit `params` entirely or pass
158
+ only the parameters you care about:
159
+
160
+ ```python
161
+ # All defaults — just effect names
162
+ chain = multiafx.FXChain([
163
+ {"effect": "sox_highpass"},
164
+ {"effect": "sox_compand"},
165
+ {"effect": "sox_reverb"},
166
+ ])
167
+
168
+ # .add() with no kwargs also works
169
+ chain = multiafx.FXChain()
170
+ chain.add("sox_highpass")
171
+ chain.add("sox_compand")
172
+
173
+ # Override only the parameters you want; the rest fall back to defaults
174
+ chain = multiafx.FXChain([
175
+ {"effect": "sox_highpass", "params": {"frequency": 200.0}}, # width_q defaults
176
+ {"effect": "sox_compand"}, # all defaults
177
+ ])
178
+ ```
179
+
180
+ ---
181
+
182
+ ## Random chain generation
183
+
184
+ ```python
185
+ import multiafx
186
+
187
+ chain = multiafx.generate_random_chain(
188
+ num_fx=8, # or (1, 8) for a random range
189
+ seed=42,
190
+ exclude_categories=["PITCH", "TIME"], # skip content-altering effects
191
+ exclude_effects=["sox_reverb"],
192
+ no_consecutive_same_category=True, # no EQ → EQ back-to-back
193
+ )
194
+ ```
195
+
196
+ ---
197
+
198
+ ## Registry introspection
199
+
200
+ ```python
201
+ import multiafx
202
+
203
+ multiafx.registry.list_effects() # all 85 names
204
+ multiafx.registry.list_effects(library="sox") # filter by library
205
+ multiafx.registry.list_effects(category="EQ") # filter by macro category
206
+ multiafx.registry.libraries() # 7 libraries
207
+ multiafx.registry.categories() # 12 MacroCategory enums
208
+
209
+ eff = multiafx.registry.get("sox_compand")
210
+ eff.name # "sox_compand"
211
+ eff.library # "sox"
212
+ eff.macro_category # <MacroCategory.DYNAMICS: 'DYNAMICS'>
213
+ eff.param_ranges # {"attack_time": ParamRange(0.001, 0.1), ...}
214
+ ```
215
+
216
+ ---
217
+
218
+ ## Effect Categories
219
+
220
+
221
+ | Category | Count | Example effects |
222
+ | ---------- | ----- | ------------------------------------------------------------------ |
223
+ | EQ | 37 | `sox_highpass`, `am_peaking_filter`, `ta_equalizer_biquad` |
224
+ | DYNAMICS | 11 | `sox_compand`, `sox_gain`, `am_normalize` |
225
+ | DISTORTION | 5 | `sox_overdrive`, `am_tanh_distortion`, `am_bit_crush` |
226
+ | REVERB | 2 | `sox_reverb`, `am_air_absorption` |
227
+ | DELAY | 2 | `sox_echo`, `sox_echos` |
228
+ | MODULATION | 4 | `sox_chorus`, `sox_flanger`, `sox_phaser`, `sox_tremolo` |
229
+ | PITCH | 3 | `sox_pitch`, `lib_pitch_shift`, `am_pitch_shift` |
230
+ | TIME | 4 | `sox_tempo`, `sox_speed`, `lib_time_stretch`, `am_time_stretch` |
231
+ | SPECTRAL | 5 | `sox_deemph`, `ta_riaa_biquad`, `lib_preemphasis` |
232
+ | STEREO | 4 | `sox_oops`, `sox_earwax`, `npy_lr_pan`, `npy_stereo_widener` |
233
+ | NOISE | 2 | `am_add_gaussian_noise`, `am_add_color_noise` |
234
+ | OTHER | 4 | `am_polarity_inversion`, `am_reverse`, `sox_dcshift`, `ta_dcshift` |
235
+
236
+
237
+ The full effect reference is in [docs/effects.md](docs/effects.md).
238
+
239
+ ---
240
+
241
+ ## Audio format
242
+
243
+ - All effects take and return `np.ndarray` with shape `(channels, samples)` and dtype `float32`.
244
+ - Sample rate is a second positional argument.
245
+ - Applying an empty chain returns the input clipped to `[-1, 1]`.
246
+
247
+ ---
248
+
249
+ ## Development
250
+
251
+ ```bash
252
+ git clone https://github.com/barry-mir/multiafx
253
+ cd multiafx
254
+ conda create -n multiafx python=3.10 -y
255
+ conda activate multiafx
256
+ pip install -e ".[dev]"
257
+ pytest tests/
258
+ ```
259
+
260
+ The test suite runs every one of the 85 effects at min / mid / max of each parameter range. **No test is expected to be skipped for any reason other than "effect has no parameters"**. If you add an effect, add nothing else — the parameterized tests pick it up automatically.
261
+
262
+ ---
263
+
264
+ ## License
265
+
266
+ MIT.
@@ -0,0 +1,225 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/barry-mir/multiafx/main/assets/icon.png" alt="MultiAFX" width="200">
3
+ </p>
4
+
5
+ Unified registry of **85 audio effects** across **7 libraries** (audiomentations, sox, torchaudio, scipy, librosa, pyloudnorm, numpy), with a clean chain-application API inspired by [pedalboard](https://github.com/spotify/pedalboard).
6
+
7
+ - One call signature for every effect: `(audio, sr, **params) -> np.ndarray`
8
+ - JSON / YAML / pickle serialization of effect chains
9
+ - Pure data format: chains are lists of dicts, portable across languages
10
+ - Parameter ranges attached to every effect for random chain generation
11
+
12
+ ---
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ pip install multiafx
18
+ ```
19
+
20
+ Requires Python ≥ 3.10.
21
+
22
+ ---
23
+
24
+ ## Quickstart
25
+
26
+ ```python
27
+ import multiafx
28
+ import soundfile as sf
29
+
30
+ # Build a chain inline
31
+ chain = multiafx.FXChain([
32
+ {"effect": "sox_highpass", "params": {"frequency": 80.0, "width_q": 0.707}},
33
+ {"effect": "sox_compand", "params": {"attack_time": 0.005,
34
+ "decay_time": 0.1,
35
+ "soft_knee_db": 6.0}},
36
+ {"effect": "sox_reverb", "params": {"reverberance": 40.0,
37
+ "high_freq_damping": 50.0,
38
+ "room_scale": 60.0,
39
+ "stereo_depth": 80.0,
40
+ "pre_delay": 20.0,
41
+ "wet_gain": -6.0}},
42
+ ])
43
+
44
+ # Load audio as (channels, samples) float32
45
+ audio, sr = sf.read("input.wav", always_2d=True)
46
+ audio = audio.T.astype("float32")
47
+
48
+ # Apply — pedalboard style
49
+ processed = chain(audio, sr)
50
+
51
+ sf.write("output.wav", processed.T, sr)
52
+ ```
53
+
54
+ ---
55
+
56
+ ## Loading chains from files
57
+
58
+ ```python
59
+ chain = multiafx.FXChain.load("preset.json") # auto-detect by extension
60
+ chain = multiafx.FXChain.load("preset.yaml")
61
+ chain = multiafx.FXChain.load("preset.pkl")
62
+
63
+ # or explicitly
64
+ chain = multiafx.FXChain.from_json("preset.json")
65
+ chain = multiafx.FXChain.from_yaml("preset.yaml")
66
+ chain = multiafx.FXChain.from_pickle("preset.pkl")
67
+ ```
68
+
69
+ **JSON format** (`preset.json`):
70
+
71
+ ```json
72
+ [
73
+ {"effect": "sox_highpass", "params": {"frequency": 80.0, "width_q": 0.707}},
74
+ {"effect": "sox_compand", "params": {"attack_time": 0.005,
75
+ "decay_time": 0.1,
76
+ "soft_knee_db": 6.0}}
77
+ ]
78
+ ```
79
+
80
+ **YAML format** (`preset.yaml`):
81
+
82
+ ```yaml
83
+ - effect: sox_highpass
84
+ params: {frequency: 80.0, width_q: 0.707}
85
+ - effect: sox_compand
86
+ params:
87
+ attack_time: 0.005
88
+ decay_time: 0.1
89
+ soft_knee_db: 6.0
90
+ ```
91
+
92
+ Saving is symmetric: `chain.to_json(path)`, `chain.to_yaml(path)`, `chain.to_pickle(path)`.
93
+
94
+ ---
95
+
96
+ ## Building chains programmatically
97
+
98
+ ```python
99
+ chain = multiafx.FXChain()
100
+ chain.add("sox_highpass", frequency=100.0, width_q=0.707)
101
+ chain.add("sox_compand", attack_time=0.005, decay_time=0.1, soft_knee_db=4.0)
102
+ chain.add("sox_reverb", reverberance=30.0, high_freq_damping=50.0, room_scale=40.0,
103
+ stereo_depth=100.0, pre_delay=15.0, wet_gain=-8.0)
104
+
105
+ # List-like mutation
106
+ chain.append({"effect": "sox_gain", "params": {"gain_db": 2.0}})
107
+ chain.insert(0, {"effect": "sox_highpass", "params": {"frequency": 40.0, "width_q": 0.7}})
108
+ del chain[-1]
109
+ chain.pop()
110
+ len(chain)
111
+ for step in chain: ...
112
+ ```
113
+
114
+ ### Default parameters
115
+
116
+ Every effect has sensible defaults, so you can omit `params` entirely or pass
117
+ only the parameters you care about:
118
+
119
+ ```python
120
+ # All defaults — just effect names
121
+ chain = multiafx.FXChain([
122
+ {"effect": "sox_highpass"},
123
+ {"effect": "sox_compand"},
124
+ {"effect": "sox_reverb"},
125
+ ])
126
+
127
+ # .add() with no kwargs also works
128
+ chain = multiafx.FXChain()
129
+ chain.add("sox_highpass")
130
+ chain.add("sox_compand")
131
+
132
+ # Override only the parameters you want; the rest fall back to defaults
133
+ chain = multiafx.FXChain([
134
+ {"effect": "sox_highpass", "params": {"frequency": 200.0}}, # width_q defaults
135
+ {"effect": "sox_compand"}, # all defaults
136
+ ])
137
+ ```
138
+
139
+ ---
140
+
141
+ ## Random chain generation
142
+
143
+ ```python
144
+ import multiafx
145
+
146
+ chain = multiafx.generate_random_chain(
147
+ num_fx=8, # or (1, 8) for a random range
148
+ seed=42,
149
+ exclude_categories=["PITCH", "TIME"], # skip content-altering effects
150
+ exclude_effects=["sox_reverb"],
151
+ no_consecutive_same_category=True, # no EQ → EQ back-to-back
152
+ )
153
+ ```
154
+
155
+ ---
156
+
157
+ ## Registry introspection
158
+
159
+ ```python
160
+ import multiafx
161
+
162
+ multiafx.registry.list_effects() # all 85 names
163
+ multiafx.registry.list_effects(library="sox") # filter by library
164
+ multiafx.registry.list_effects(category="EQ") # filter by macro category
165
+ multiafx.registry.libraries() # 7 libraries
166
+ multiafx.registry.categories() # 12 MacroCategory enums
167
+
168
+ eff = multiafx.registry.get("sox_compand")
169
+ eff.name # "sox_compand"
170
+ eff.library # "sox"
171
+ eff.macro_category # <MacroCategory.DYNAMICS: 'DYNAMICS'>
172
+ eff.param_ranges # {"attack_time": ParamRange(0.001, 0.1), ...}
173
+ ```
174
+
175
+ ---
176
+
177
+ ## Effect Categories
178
+
179
+
180
+ | Category | Count | Example effects |
181
+ | ---------- | ----- | ------------------------------------------------------------------ |
182
+ | EQ | 37 | `sox_highpass`, `am_peaking_filter`, `ta_equalizer_biquad` |
183
+ | DYNAMICS | 11 | `sox_compand`, `sox_gain`, `am_normalize` |
184
+ | DISTORTION | 5 | `sox_overdrive`, `am_tanh_distortion`, `am_bit_crush` |
185
+ | REVERB | 2 | `sox_reverb`, `am_air_absorption` |
186
+ | DELAY | 2 | `sox_echo`, `sox_echos` |
187
+ | MODULATION | 4 | `sox_chorus`, `sox_flanger`, `sox_phaser`, `sox_tremolo` |
188
+ | PITCH | 3 | `sox_pitch`, `lib_pitch_shift`, `am_pitch_shift` |
189
+ | TIME | 4 | `sox_tempo`, `sox_speed`, `lib_time_stretch`, `am_time_stretch` |
190
+ | SPECTRAL | 5 | `sox_deemph`, `ta_riaa_biquad`, `lib_preemphasis` |
191
+ | STEREO | 4 | `sox_oops`, `sox_earwax`, `npy_lr_pan`, `npy_stereo_widener` |
192
+ | NOISE | 2 | `am_add_gaussian_noise`, `am_add_color_noise` |
193
+ | OTHER | 4 | `am_polarity_inversion`, `am_reverse`, `sox_dcshift`, `ta_dcshift` |
194
+
195
+
196
+ The full effect reference is in [docs/effects.md](docs/effects.md).
197
+
198
+ ---
199
+
200
+ ## Audio format
201
+
202
+ - All effects take and return `np.ndarray` with shape `(channels, samples)` and dtype `float32`.
203
+ - Sample rate is a second positional argument.
204
+ - Applying an empty chain returns the input clipped to `[-1, 1]`.
205
+
206
+ ---
207
+
208
+ ## Development
209
+
210
+ ```bash
211
+ git clone https://github.com/barry-mir/multiafx
212
+ cd multiafx
213
+ conda create -n multiafx python=3.10 -y
214
+ conda activate multiafx
215
+ pip install -e ".[dev]"
216
+ pytest tests/
217
+ ```
218
+
219
+ The test suite runs every one of the 85 effects at min / mid / max of each parameter range. **No test is expected to be skipped for any reason other than "effect has no parameters"**. If you add an effect, add nothing else — the parameterized tests pick it up automatically.
220
+
221
+ ---
222
+
223
+ ## License
224
+
225
+ MIT.
@@ -0,0 +1,61 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "multiafx"
7
+ version = "0.1.0"
8
+ description = "Multi-library audio effects registry with a unified chain-application API"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ authors = [
12
+ {name = "Barry Cheng", email = "im31132@gmail.com"},
13
+ ]
14
+ requires-python = ">=3.10"
15
+ keywords = ["audio", "dsp", "effects", "signal-processing", "augmentation"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "Intended Audience :: Science/Research",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Topic :: Multimedia :: Sound/Audio",
26
+ "Topic :: Scientific/Engineering",
27
+ ]
28
+ dependencies = [
29
+ "numpy>=1.24,<3",
30
+ "scipy>=1.10",
31
+ "pyyaml>=6.0",
32
+ "audiomentations>=0.43.1",
33
+ "sox>=1.4",
34
+ "torch>=2.0",
35
+ "torchaudio>=2.0",
36
+ "librosa>=0.10",
37
+ "pyloudnorm>=0.1.1",
38
+ "loudness>=0.2",
39
+ ]
40
+
41
+ [project.optional-dependencies]
42
+ dev = [
43
+ "pytest>=7.0",
44
+ "pytest-cov",
45
+ "soundfile>=0.12",
46
+ "build>=1.0",
47
+ "twine>=4.0",
48
+ ]
49
+
50
+ [project.urls]
51
+ Homepage = "https://github.com/barry-mir/multiafx"
52
+ Repository = "https://github.com/barry-mir/multiafx"
53
+ Issues = "https://github.com/barry-mir/multiafx/issues"
54
+
55
+ [tool.setuptools.packages.find]
56
+ where = ["src"]
57
+
58
+ [tool.pytest.ini_options]
59
+ testpaths = ["tests"]
60
+ python_files = ["test_*.py"]
61
+ addopts = ["-v", "--tb=short"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,18 @@
1
+ """multiafx: multi-library audio effects with a unified chain-application API."""
2
+
3
+ from multiafx._version import __version__
4
+ from multiafx.apply import apply_chain
5
+ from multiafx.chain import FXChain, generate_random_chain
6
+ from multiafx.registry import registry
7
+ from multiafx.types import EffectDef, MacroCategory, ParamRange
8
+
9
+ __all__ = [
10
+ "__version__",
11
+ "FXChain",
12
+ "generate_random_chain",
13
+ "apply_chain",
14
+ "registry",
15
+ "EffectDef",
16
+ "MacroCategory",
17
+ "ParamRange",
18
+ ]
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,39 @@
1
+ """Apply a chain of effects to an audio array."""
2
+
3
+ import numpy as np
4
+
5
+ from multiafx.registry import registry, INTEGER_PARAMS
6
+
7
+
8
+ def apply_chain(audio: np.ndarray, sr: int, chain: list[dict]) -> np.ndarray:
9
+ """Apply a sequence of effects to audio.
10
+
11
+ No error handling — the first failing effect raises. This is intentional:
12
+ if an effect fails we want the call site to know exactly which one.
13
+
14
+ Args:
15
+ audio: (channels, samples) float32 array.
16
+ sr: Sample rate.
17
+ chain: List of dicts, each with keys ``effect`` (str) and ``params`` (dict).
18
+
19
+ Returns:
20
+ Processed (channels, samples) float32 array, clipped to [-1, 1].
21
+ """
22
+ x = audio.astype(np.float32)
23
+
24
+ for step in chain:
25
+ name = step["effect"]
26
+ params = dict(step.get("params", {}))
27
+
28
+ # Coerce integer-only params
29
+ for p in INTEGER_PARAMS:
30
+ if p in params:
31
+ params[p] = int(round(params[p]))
32
+
33
+ effect_def = registry.get(name)
34
+ x = effect_def.callable(x, sr, **params)
35
+
36
+ if not np.all(np.isfinite(x)):
37
+ raise ValueError(f"Effect {name!r} produced NaN/Inf output")
38
+
39
+ return np.clip(x, -1.0, 1.0).astype(np.float32)