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 installer → Real 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:
DynamicComplexity — essentia-specific, no equivalent anywhere. Port from the C++ source.
SpectralContrast — librosa has one, but with different band edges and valley definition, so not numerically interchangeable.
Dissonance — Plomp & Levelt roughness over spectral peaks. Published, ~50 lines.
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
- Close the cross-validation gaps on macOS/Linux against the current essentia output, so there is a numeric reference to port against.
- 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.
- 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.
- Implement the Windows backend: pyloudnorm + pyebur128 + scipy/numpy, then the four ports.
- Resolve the Python version question and update both installers.
- Flip
Real install on Windows to blocking in .github/workflows/install-windows.yml and drop continue-on-error.
- Fix the
Operating System :: OS Independent classifier in pyproject.toml:31.
Related
Problem
phantom-audiocannot be installed on Windows. Not "is untested on" — cannot be installed, and never could be.essentia==2.1b6.dev1389is a hard core dependency (pyproject.toml:37) and essentia has never published a Windows wheel for any version, including the current2.1b6.dev1438. It also has no sdist since 2020, so pip does not attempt a source build — resolution simply fails with no candidate distributions.Reproduce:
uv pip compile --python-platform x86_64-pc-windows-msvc --python-version 3.13on a requirements file containingphantom-audio.Meanwhile the README advertises a Windows install,
install.ps1exists and reports telemetry, andpyproject.tomlclaimsOperating System :: OS Independent. The CI job added in #51 (Windows installer→Real install on Windows) demonstrates the failure on a realwindows-latestrunner and is markedcontinue-on-erroruntil 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-separationfails only through its own dependency back onphantom-audio.Paths that are closed
thomasfillon/essentia2.1_beta4, linux-64, from 2017.RogueVectorreaching into MSVC STL internals, libyaml__declspeccollisions, Eigen expression templates hanging/O2, C++ alternative tokens,Edt::BOOLvs the Windows SDKBOOL, 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/._bands.py:171-218_octave_band_energiesWindowing,Spectrum,FrequencyBands,FrameGeneratorspectral.py:62-168analyze_spectrumCentroid,RollOff,Flatness,SpectralContrast,SpectralPeaks,Dissonanceloudness.py:99-175analyze_loudnessLoudnessEBUR128_truepeak.py:27-57channel_true_peaksTruePeakDetectordynamics.py:53-126analyze_dynamicsDynamicComplexityproblems.py:538-597_detect_humHumDetectorAlready 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. Realwin_amd64wheels.Spectrum,Centroid,Flatness,RollOff— match plain numpy to 1e-6, verified.Genuinely needs porting:
DynamicComplexity— essentia-specific, no equivalent anywhere. Port from the C++ source.SpectralContrast— librosa has one, but with different band edges and valley definition, so not numerically interchangeable.Dissonance— Plomp & Levelt roughness over spectral peaks. Published, ~50 lines.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), andes.FrameGeneratordefaults tostartFromZero=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-25importsloudness,spectral,dynamics,problemsat package import. Without essentia, every module in thephantomnamespace fails to import, includingphantom doctor— whose entire job is to report missing dependencies. Verified with asys.meta_pathblocker. Only 9 of the 20 MCP tools genuinely need essentia; the other 11 break solely through this chain. Note also the trap atcli/__init__.py:100:except ModuleNotFoundError: passwill 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 covertrue_peak_dbtp,loudness_range_lu, or the momentary/short-term series.test_spectral_crossvalidation.py— directional assertions only. DeclaresTOLERANCE_DB = 3.0at line 15 and never uses it. No numeric coverage ofspectral_rolloff_hz,spectral_contrast, ordissonance.test_dynamics_crossvalidation.py— covers only the numpy-computed fields. Zero coverage ofdynamic_complexityorloudness_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
masking.py:31-44,:177-179,:183-187,:200). essentia only supplies band energies as input.comparison.py:272-277subtracts 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 correctcompare_to_profile/compare_to_referenceresults.pyebur128ships 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 bundledffmpeg.exewithpeak=true(note that ffmpeg'sebur128filter reports sample peak unless that flag is passed, with no warning).Suggested sequence
CORE_DEPS(_diagnostics.py:5-14); fix__init__.pyso the package imports without it andphantom doctorreports it as missing rather than crashing.fn(audio, settings) -> BaseModelshape. No such indirection exists today — theAnalysisSpecregistry infacade.py:101-162maps dimensions to concrete functions, not engines.Real install on Windowsto blocking in.github/workflows/install-windows.ymland dropcontinue-on-error.Operating System :: OS Independentclassifier inpyproject.toml:31.Related