Development

Repository layout

  • src/faustax/dsp/*.dsp — the Faust sources. These files are the reference for each effect. Most files contain a few lines that import stdfaust.

  • src/faustax/_generated/ — the AOT-compiled Flax NNX modules. The repository contains these files so that end users do not need the Faust compiler. Each file records the compiler version in its header. Do not edit these files by hand. Do not import from this directory. faustax.modules is the public interface to these classes.

  • src/faustax/modules.py — public re-exports of the generated classes. This module is the supported import path for raw-module workflows. Examples of these workflows are streaming, NNX-native training, and synthesizers.

  • src/faustax/processors.py — the batched, differentiable wrappers.

  • src/faustax/functional.py — the dasp-style functional entry points.

  • src/faustax/reverb.py — the noise-shaped reverb. This reverb is the only hand-written JAX processor. Its synthesized FIR has no per-sample recurrence.

  • src/faustax/audiotree.py — the audiotree transform adapter (FaustFx).

  • src/faustax/fx.py — the argbind-ready transform factories.

  • src/faustax/ops.py — the custom-VJP recursive filter primitives.

  • src/faustax/compile.py — runtime compilation of arbitrary DSP.

  • src/faustax/vectorize/ — the AST pass that rerolls scalar-unrolled par() banks into vector ops. The stages run in this order: signatures, banks, reroll, scheduling, driver. signatures computes a structural signature with holes. banks does the bookkeeping. reroll does the grouping and the rewriting. scheduling does the carry allocation and the reordering. driver contains vectorize_source() and the CLI.

  • examples/ — runnable scripts. The test suite imports these scripts as its fixtures (see examples/README.md).

Regenerating the modules

This procedure requires a Faust build from the master-dev branch. The NNX backend is only on that branch until the backend reaches a tagged release. A Faust build from Homebrew or apt does not have the NNX backend. The output of faust --version must list “DSP to NNX”. See CONTRIBUTING.md for the build steps.

FAUST_BIN=/path/to/faust uv run python tools/generate.py

tools/generate.py compiles each src/faustax/dsp/*.dsp file with faust -lang nnx -a jax/minimal.py. The declare name line of each file names the generated class, so the source decides the spelling (ParametricEQ, DiffVoxFDN). Name a stem on the command line to regenerate only that module. The default sweep skips the sources that reproduce compiler behaviour, because the package does not ship them. The script prepends a header that records the compiler version. The script also rewrites the compiler’s absolute paths to the <faust> and <repo> placeholders. As a result, a regenerated module does not record the machine that built it. The --scrub-only option applies this rewrite and does not recompile.

Licensing

The generated modules embed code from the Faust standard library. This code has per-function licenses. These licenses must stay with the code. The NOTICE file records all of these licenses. CI runs tools/collect_attribution.py --check. This check fails when the NOTICE file does not list a newly used library. If a new .dsp file imports a library that is new to the repository, update the NOTICE file. See CONTRIBUTING.md.

Adding an effect

  1. Write src/faustax/dsp/<name>.dsp. The declare name line becomes the generated class name and the prefix of every parameter address. Select the slider labels carefully. The labels become the parameter names in all locations. Examples of these locations are the wrapper kwargs and the argbind YAML keys. To make a parameter time-varying, lift its slider to an input channel with widget modulation. Parameter estimation describes this construction. faustax.compile_file compiles a .dsp file in the running process, which is the faster loop while you iterate on the design.

  2. Regenerate the modules with the procedure above.

  3. Add the generated class to modules.py. Add a Processor subclass in processors.py. The subclass is two lines. Export the subclass. For one-in/one-out effects, add a factory line in fx.py.

  4. Add behavioral tests. Test against a reference implementation or a measurable property. Examples of measurable properties are the magnitude response and the static curve. Do not test implementation details. A filter at its neutral setting is not always a waveform identity. The RBJ biquad sections are waveform identities at their neutral settings. The JOS-style shelves are allpass at 0 dB. If you are not sure, test the magnitude response.

Testing

uv run pytest                    # behavioral + adapter + argbind tests
uv run make -C docs doctest      # the runnable examples in these docs
uv run python examples/benchmarks/bench.py

The Sphinx doctest builder runs the examples in this documentation (make -C docs doctest). CI runs this builder together with the test suite. An outdated example causes a build failure.