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.
- One call signature for every effect:
(audio, sr, **params) -> np.ndarray - JSON / YAML / pickle serialization of effect chains
- Pure data format: chains are lists of dicts, portable across languages
- Parameter ranges attached to every effect for random chain generation
pip install multiafxRequires Python ≥ 3.10.
import multiafx
import soundfile as sf
# Build a chain inline
chain = multiafx.FXChain([
{"effect": "sox_highpass", "params": {"frequency": 80.0, "width_q": 0.707}},
{"effect": "sox_compand", "params": {"attack_time": 0.005,
"decay_time": 0.1,
"soft_knee_db": 6.0}},
{"effect": "sox_reverb", "params": {"reverberance": 40.0,
"high_freq_damping": 50.0,
"room_scale": 60.0,
"stereo_depth": 80.0,
"pre_delay": 20.0,
"wet_gain": -6.0}},
])
# Load audio as (channels, samples) float32
audio, sr = sf.read("input.wav", always_2d=True)
audio = audio.T.astype("float32")
# Apply — pedalboard style
processed = chain(audio, sr)
sf.write("output.wav", processed.T, sr)chain = multiafx.FXChain.load("preset.json") # auto-detect by extension
chain = multiafx.FXChain.load("preset.yaml")
chain = multiafx.FXChain.load("preset.pkl")
# or explicitly
chain = multiafx.FXChain.from_json("preset.json")
chain = multiafx.FXChain.from_yaml("preset.yaml")
chain = multiafx.FXChain.from_pickle("preset.pkl")JSON format (preset.json):
[
{"effect": "sox_highpass", "params": {"frequency": 80.0, "width_q": 0.707}},
{"effect": "sox_compand", "params": {"attack_time": 0.005,
"decay_time": 0.1,
"soft_knee_db": 6.0}}
]YAML format (preset.yaml):
- effect: sox_highpass
params: {frequency: 80.0, width_q: 0.707}
- effect: sox_compand
params:
attack_time: 0.005
decay_time: 0.1
soft_knee_db: 6.0Saving is symmetric: chain.to_json(path), chain.to_yaml(path), chain.to_pickle(path).
chain = multiafx.FXChain()
chain.add("sox_highpass", frequency=100.0, width_q=0.707)
chain.add("sox_compand", attack_time=0.005, decay_time=0.1, soft_knee_db=4.0)
chain.add("sox_reverb", reverberance=30.0, high_freq_damping=50.0, room_scale=40.0,
stereo_depth=100.0, pre_delay=15.0, wet_gain=-8.0)
# List-like mutation
chain.append({"effect": "sox_gain", "params": {"gain_db": 2.0}})
chain.insert(0, {"effect": "sox_highpass", "params": {"frequency": 40.0, "width_q": 0.7}})
del chain[-1]
chain.pop()
len(chain)
for step in chain: ...Every effect has sensible defaults, so you can omit params entirely or pass
only the parameters you care about:
# All defaults — just effect names
chain = multiafx.FXChain([
{"effect": "sox_highpass"},
{"effect": "sox_compand"},
{"effect": "sox_reverb"},
])
# .add() with no kwargs also works
chain = multiafx.FXChain()
chain.add("sox_highpass")
chain.add("sox_compand")
# Override only the parameters you want; the rest fall back to defaults
chain = multiafx.FXChain([
{"effect": "sox_highpass", "params": {"frequency": 200.0}}, # width_q defaults
{"effect": "sox_compand"}, # all defaults
])import multiafx
chain = multiafx.generate_random_chain(
num_fx=8, # or (1, 8) for a random range
seed=42,
exclude_categories=["PITCH", "TIME"], # skip content-altering effects
exclude_effects=["sox_reverb"],
no_consecutive_same_category=True, # no EQ → EQ back-to-back
)import multiafx
multiafx.registry.list_effects() # all 85 names
multiafx.registry.list_effects(library="sox") # filter by library
multiafx.registry.list_effects(category="EQ") # filter by macro category
multiafx.registry.libraries() # 7 libraries
multiafx.registry.categories() # 12 MacroCategory enums
eff = multiafx.registry.get("sox_compand")
eff.name # "sox_compand"
eff.library # "sox"
eff.macro_category # <MacroCategory.DYNAMICS: 'DYNAMICS'>
eff.param_ranges # {"attack_time": ParamRange(0.001, 0.1), ...}| Category | Count | Example effects |
|---|---|---|
| EQ | 37 | sox_highpass, am_peaking_filter, ta_equalizer_biquad |
| DYNAMICS | 11 | sox_compand, sox_gain, am_normalize |
| DISTORTION | 5 | sox_overdrive, am_tanh_distortion, am_bit_crush |
| REVERB | 2 | sox_reverb, am_air_absorption |
| DELAY | 2 | sox_echo, sox_echos |
| MODULATION | 4 | sox_chorus, sox_flanger, sox_phaser, sox_tremolo |
| PITCH | 3 | sox_pitch, lib_pitch_shift, am_pitch_shift |
| TIME | 4 | sox_tempo, sox_speed, lib_time_stretch, am_time_stretch |
| SPECTRAL | 5 | sox_deemph, ta_riaa_biquad, lib_preemphasis |
| STEREO | 4 | sox_oops, sox_earwax, npy_lr_pan, npy_stereo_widener |
| NOISE | 2 | am_add_gaussian_noise, am_add_color_noise |
| OTHER | 4 | am_polarity_inversion, am_reverse, sox_dcshift, ta_dcshift |
The full effect reference is in docs/effects.md.
- All effects take and return
np.ndarraywith shape(channels, samples)and dtypefloat32. - Sample rate is a second positional argument.
- Applying an empty chain returns the input clipped to
[-1, 1].
git clone https://github.com/barry-mir/multiafx
cd multiafx
conda create -n multiafx python=3.10 -y
conda activate multiafx
pip install -e ".[dev]"
pytest tests/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.
MIT.