Skip to content

Windows support: replace essentia with a Windows-capable analysis backend #52

Description

@leesaenz

Problem

phantom-audio cannot be installed on Windows. Not "is untested on" — cannot be installed, and never could be.

essentia==2.1b6.dev1389 is a hard core dependency (pyproject.toml:37) and essentia has never published a Windows wheel for any version, including the current 2.1b6.dev1438. It also has no sdist since 2020, so pip does not attempt a source build — resolution simply fails with no candidate distributions.

× No solution found when resolving dependencies:
╰─▶ Because essentia==2.1b6.dev1389 has no wheels with a matching platform
    tag (e.g., `win_amd64`) and all versions of phantom-audio depend on
    essentia==2.1b6.dev1389, we can conclude that all versions of
    phantom-audio cannot be used.

Reproduce: uv pip compile --python-platform x86_64-pc-windows-msvc --python-version 3.13 on a requirements file containing phantom-audio.

Meanwhile the README advertises a Windows install, install.ps1 exists and reports telemetry, and pyproject.toml claims Operating System :: OS Independent. The CI job added in #51 (Windows installerReal install on Windows) demonstrates the failure on a real windows-latest runner and is marked continue-on-error until this is fixed.

essentia is the only blocker

Every other dependency resolves on Windows, verified individually with the same platform-targeted resolver: numpy, soundfile, pydantic, scipy, fastmcp, click, rich, rich-click, plotext, ffmpeg-progress-yield, plus librosa, pyloudnorm, matchering, pedalboard, demucs and torch. phantom-audio-separation fails only through its own dependency back on phantom-audio.

Paths that are closed

  • conda — there is no essentia on conda-forge. No package, no feedstock, and no recipe has ever been submitted to staged-recipes. The only conda build anywhere is thomasfillon/essentia 2.1_beta4, linux-64, from 2017.
  • Prebuilt community wheels — several people have built working Windows wheels, but only ever published them as GitHub Actions artifacts, which expire after 90 days. Every such link (zak-45, regorxxx) is now dead.
  • Waiting for upstream — the installation docs have said "Python bindings are not supported yet" on Windows unchanged since 2015. PR #1514 (Windows AMD64 wheels via cibuildwheel + MSVC + vcpkg, opened 2026-05-24) is mergeable with zero reviews and zero comments after three months, on a repo that is otherwise actively committed to. #1398 has been open since Feb 2024 after a maintainer said they would look at it soon. wo80's PR #1482 was closed unmerged in Aug 2025.
  • Maintaining our own fork's wheels — technically possible (wo80's CMake fork plus PR #1514 enumerate every MSVC breakage: RogueVector reaching into MSVC STL internals, libyaml __declspec collisions, Eigen expression templates hanging /O2, C++ alternative tokens, Edt::BOOL vs the Windows SDK BOOL, C99 VLAs). Rejected because it means owning a Windows build of someone else's C++ library on a fork, re-owned on every essentia bump — which is precisely what every previous attempt failed to sustain.

Scope of the replacement

14 essentia algorithms across 6 modules, ~397 lines, about 3.6% of src/.

Module Function Algorithms
_bands.py:171-218 _octave_band_energies Windowing, Spectrum, FrequencyBands, FrameGenerator
spectral.py:62-168 analyze_spectrum Centroid, RollOff, Flatness, SpectralContrast, SpectralPeaks, Dissonance
loudness.py:99-175 analyze_loudness LoudnessEBUR128
_truepeak.py:27-57 channel_true_peaks TruePeakDetector
dynamics.py:53-126 analyze_dynamics DynamicComplexity
problems.py:538-597 _detect_hum HumDetector

Already solved by Windows-capable libraries:

  • pyloudnorm (already a dev dep) — integrated LUFS + LRA. Pure Python.
  • pyebur128 — momentary/short-term LUFS and true peak, via libebur128, the reference implementation ffmpeg uses. Real win_amd64 wheels.
  • Spectrum, Centroid, Flatness, RollOff — match plain numpy to 1e-6, verified.

Genuinely needs porting:

  1. DynamicComplexity — essentia-specific, no equivalent anywhere. Port from the C++ source.
  2. SpectralContrast — librosa has one, but with different band edges and valley definition, so not numerically interchangeable.
  3. Dissonance — Plomp & Levelt roughness over spectral peaks. Published, ~50 lines.
  4. HumDetector — no equivalent, but phantom only uses the output to find 50/60 Hz plus 5 harmonics (problems.py:562-573), which a Welch PSD with peak picking covers.

Two framing conventions that will silently shift every averaged number if missed: es.Windowing(type="hann") is normalized (a 1024-point window sums to 2.0, not 512), and es.FrameGenerator defaults to startFromZero=False, so the first frame is half zero-padded and centred on sample 0. For 10000 samples at 4096/2048 that yields 6 frames where naive framing yields 4, and the padded frames are included in cross-frame means.

Blockers to fix first

Eager imports. __init__.py:20-25 imports loudness, spectral, dynamics, problems at package import. Without essentia, every module in the phantom namespace fails to import, including phantom doctor — whose entire job is to report missing dependencies. Verified with a sys.meta_path blocker. Only 9 of the 20 MCP tools genuinely need essentia; the other 11 break solely through this chain. Note also the trap at cli/__init__.py:100: except ModuleNotFoundError: pass will silently drop any command whose module fails to import, so a partial refactor makes commands vanish with no error.

Test-harness gaps line up exactly with the algorithms that have no library equivalent.

  • test_loudness_crossvalidation.py — strong. Validates integrated LUFS against pyloudnorm at ±0.5 LU across 8 signals. Does not cover true_peak_dbtp, loudness_range_lu, or the momentary/short-term series.
  • test_spectral_crossvalidation.py — directional assertions only. Declares TOLERANCE_DB = 3.0 at line 15 and never uses it. No numeric coverage of spectral_rolloff_hz, spectral_contrast, or dissonance.
  • test_dynamics_crossvalidation.py — covers only the numpy-computed fields. Zero coverage of dynamic_complexity or loudness_db, the only two essentia-derived fields in that module.

Close these before swapping anything, or a drifting replacement will pass silently.

Two things that reduce risk

  • The patent-pending weighted masking analysis is unaffected. The novel weighting is pure numpy (masking.py:31-44, :177-179, :183-187, :200). essentia only supplies band energies as input.
  • Profile comparison is immune to absolute offsets. comparison.py:272-277 subtracts the mean before comparing, and the shipped profiles store relative band emphasis rather than absolute dB. A backend whose band energies are offset by a constant still produces correct compare_to_profile / compare_to_reference results.

⚠️ Python version trap

pyebur128 ships wheels only through cp312, and librosa 1.0 requires Python ≥3.12. 3.12 is the only version where both arrive as wheels. Both installers currently pin --python 3.13. Either target 3.12 on Windows, vendor libebur128, or shell out to a bundled ffmpeg.exe with peak=true (note that ffmpeg's ebur128 filter reports sample peak unless that flag is passed, with no warning).

Suggested sequence

  1. Close the cross-validation gaps on macOS/Linux against the current essentia output, so there is a numeric reference to port against.
  2. Make essentia lazy and move it out of CORE_DEPS (_diagnostics.py:5-14); fix __init__.py so the package imports without it and phantom doctor reports it as missing rather than crashing.
  3. Introduce a backend interface behind the six analyzer functions, which already share a clean fn(audio, settings) -> BaseModel shape. No such indirection exists today — the AnalysisSpec registry in facade.py:101-162 maps dimensions to concrete functions, not engines.
  4. Implement the Windows backend: pyloudnorm + pyebur128 + scipy/numpy, then the four ports.
  5. Resolve the Python version question and update both installers.
  6. Flip Real install on Windows to blocking in .github/workflows/install-windows.yml and drop continue-on-error.
  7. Fix the Operating System :: OS Independent classifier in pyproject.toml:31.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions