diff --git a/.github/workflows/all.yml b/.github/workflows/all.yml index 72325145..b59893e6 100644 --- a/.github/workflows/all.yml +++ b/.github/workflows/all.yml @@ -351,7 +351,6 @@ jobs: CCACHE_IGNOREOPTIONS: "-fno-strict-overflow -fwrapv -W* -arch x86_64 arm64 -dynamic -fno-common -g -I/usr/local/opt/*" CCACHE_LOGFILE: ${{ github.workspace }}/.ccache_log USE_ASAN: "1" - USE_PORTABLE_SIMD: "1" # Use portable SIMD to avoid "Illegal instruction" on different runner CPUs DISABLE_LTO: "1" # Speeds up un-cacheable link step which doesn't really increase performance in tests anyways CC: ${{ matrix.cc }} CXX: ${{ matrix.cxx }} @@ -798,7 +797,6 @@ jobs: env: DEBUG: "0" USE_ASAN: "1" - USE_PORTABLE_SIMD: "1" # Use portable SIMD to avoid "Illegal instruction" on different runner CPUs CCACHE_DIR: ${{ github.workspace }}/.ccache CCACHE_BASEDIR: ${{ github.workspace }} CCACHE_DEBUGDIR: ${{ github.workspace }}/.ccache_debug diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..a6aa1a65 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,97 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Pedalboard is a Python library (with C++ internals) for audio processing: reading, writing, rendering, and adding effects. Built by Spotify's Audio Intelligence Lab using pybind11 bindings over the JUCE framework. + +## Build Commands + +```bash +# Clone with submodules (required for vendors/JUCE) +git clone --recurse-submodules --shallow-submodules git@github.com:spotify/pedalboard.git + +# Install build deps and build +pip install pybind11 scikit-build-core +pip install --no-build-isolation -ve . + +# Fast debug build with ccache (macOS) +rm -rf build && CC="ccache clang" CXX="ccache clang++" DEBUG=1 python3 -m pip install -e . + +# Fast debug build with ccache (Linux, GCC) +rm -rf build && CC="ccache gcc" CXX="scripts/ccache_g++" DEBUG=1 python3 setup.py build -j8 develop +``` + +Build environment variables: `DEBUG=1`, `DISABLE_LTO`, `USE_MARCH_NATIVE`, `USE_ASAN`, `USE_TSAN`, `USE_MSAN`. + +## Testing + +```bash +# Run all tests +pytest tests/ + +# Run a single test file +pytest tests/test_python_interface.py -v + +# Run a single test +pytest tests/test_python_interface.py::test_no_transforms + +# Via tox (runs coverage) +tox +``` + +Test dependencies are in `test-requirements.txt`. CI parallelizes tests via `NUM_TEST_WORKERS` and `TEST_WORKER_INDEX` env vars (see `tests/conftest.py`). + +## Linting and Formatting + +```bash +# Python lint +ruff check pedalboard + +# Python format check +ruff format --check pedalboard --diff + +# Type checking +pyright pedalboard tests/test_*.py + +# C++ formatting (clang-format 14, LLVM style) +# Applied to pedalboard/ C++ files, excluding vendors/ +``` + +Ruff config is in `pyproject.toml`: line-length 100, target Python 3.10+. + +## Architecture + +**Python layer** (`pedalboard/`): +- `__init__.py` — re-exports from `pedalboard_native` (the compiled C++ module) +- `_pedalboard.py` — `Pedalboard` class (effect chain container) and `load_plugin()` for VST3/AU +- `io/` — `AudioFile` (read/write WAV, FLAC, MP3, OGG, AIFF) and `AudioStream` (live audio) + +**C++ native module** (`pedalboard_native`): +- `pedalboard/python_bindings.cpp` — main pybind11 entry point +- `pedalboard/plugins/*.h` — individual effect plugin implementations (Compressor, Reverb, Delay, Chorus, etc.), each wrapping a JUCE processor via `JucePlugin` template +- `pedalboard/plugin_templates/` — reusable plugin wrappers (Resample, ForceMono, etc.) +- `pedalboard/juce_overrides/` — patched JUCE module inclusions + +**Type stubs** (`pedalboard_native/*.pyi`): +- Generated via `pybind11-stubgen`, post-processed by `scripts/postprocess_type_hints` +- Update process documented in CONTRIBUTING.md + +**Vendor libraries** (`vendors/`): FFTW3, RubberBand, LAME, libgsm, libFLAC — compiled as part of the build. + +**Build system**: scikit-build-core (primary, via `pyproject.toml` + `CMakeLists.txt`) with legacy `setup.py` still functional. All `.cpp` and `.mm` files under `pedalboard/` are compiled automatically. + +## Platform Notes + +- **macOS**: Links Accelerate, AudioToolbox, CoreAudio, and other frameworks. Supports Audio Units. +- **Linux**: Requires libfreetype-dev, xorg-dev, ALSA. FFTW3 used for FFT (vendored). +- **Windows**: MSVC required. No AU support. +- C++17 required. SIMD: AVX on x86, NEON on ARM. + +## Key Design Patterns + +- Plugin architecture: `Plugin` base → `JucePlugin` template +- Container composition: `Pedalboard` extends `Chain` for serial/parallel plugin graphs +- GIL is released during C++ audio processing for thread safety +- External plugin hosting: VST3 cross-platform, Audio Units macOS-only diff --git a/CMakeLists.txt b/CMakeLists.txt index d9ae8b9f..bfa001c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -199,13 +199,14 @@ elseif(UNIX AND NOT APPLE) if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm|aarch64") add_compile_definitions(HAVE_NEON=1) else() - # Use -march=native for local builds to optimize for the current CPU, - # but use a portable baseline for CI builds to avoid "Illegal instruction" errors - # when ccache restores objects built on different runner hardware. - if(DEFINED ENV{USE_PORTABLE_SIMD}) - add_compile_options(-mavx) - else() + # Use -mavx by default for portable binaries (AVX is baseline since + # Sandy Bridge, 2011). Set USE_MARCH_NATIVE=1 to optimize for the + # local CPU instead (useful for developer builds, but the resulting + # binaries may crash on other hardware). + if(DEFINED ENV{USE_MARCH_NATIVE}) add_compile_options(-march=native) + else() + add_compile_options(-mavx) endif() add_compile_definitions(HAVE_AVX) endif() diff --git a/setup.py b/setup.py index b5536e5d..da130fef 100644 --- a/setup.py +++ b/setup.py @@ -175,13 +175,14 @@ def ignore_files_matching(files, *matches): else: # And on x86, ignore the ARM-specific SIMD code (and KCVI; not GCC or Clang compatible). fftw_paths = ignore_files_matching(fftw_paths, "neon") - # Use -march=native for local builds to optimize for the current CPU, - # but use a portable baseline for CI builds to avoid "Illegal instruction" errors - # when ccache restores objects built on different runner hardware. - if os.getenv("USE_PORTABLE_SIMD"): - ALL_CFLAGS.append("-mavx") - else: + # Use -mavx by default for portable binaries (AVX is baseline since + # Sandy Bridge, 2011). Set USE_MARCH_NATIVE=1 to optimize for the + # local CPU instead (useful for developer builds, but the resulting + # binaries may crash on other hardware). + if os.getenv("USE_MARCH_NATIVE"): ALL_CFLAGS.append("-march=native") + else: + ALL_CFLAGS.append("-mavx") # Enable SIMD instructions: ALL_CFLAGS.extend( [