Skip to content

docs(history): library review — matteospanio/torchfx - #39

Merged
slittycode merged 1 commit into
mainfrom
claude/review-torchfx-inspiration-LpSZq
May 14, 2026
Merged

docs(history): library review — matteospanio/torchfx#39
slittycode merged 1 commit into
mainfrom
claude/review-torchfx-inspiration-LpSZq

Conversation

@slittycode

Copy link
Copy Markdown
Owner

Adds docs/history/library-review-torchfx-2026-05-13.md recording the evaluation of matteospanio/torchfx for ASA.

TL;DR

Do not adopt as a dependency. Borrow the pattern only if a future profile shows per-band filtering on the Phase 1 hot path.

Why

  1. License blocker. ASA is MIT. torchfx is GPL v3. Linking it forces the combined work to GPL v3 on redistribution — a one-way door against any hosted/commercial path. This alone is decisive.
  2. Filter inventory is sparse. Two primitives shipped (LoButterworth, ParametricEQ). Nothing here replaces Essentia's MIR/loudness/key/BPM. The only thing on offer is nn.Module plumbing around torchaudio.functional.lfilter, which is ~50 LOC if we ever want it.
  3. Differentiability is unused. ASA's filters are analysis tools, not training targets. Autograd through bandpass is dead weight.
  4. Not on the critical path. Phase 1 currently does ≈11 scipy bandpasses per run (per-band RT60, 7-band transient density, effects detail, stereo bandpass). Cost is invisible against Demucs and Phase 2 latency. Demucs already places tensors on GPU; the latent win is device co-location between Demucs output and stem feature extraction — not GPU-accelerated bandpass for its own sake.
  5. Quality-invariant friction. Swapping the bandpass implementation under existing measurements changes filter phase and transient response, which shifts downstream onset counts and RT60 fits. Any swap needs a value-level snapshot regression on a representative corpus, not just EXPECTED_TOP_LEVEL_KEYS parity.

What's worth borrowing later (if and only if a profile justifies it)

  • Wave(tensor, fs, device) carrier object — the device field is the actual ergonomic win, enabling co-location with Demucs.
  • nn.Module filter wrappers with register_buffer for SOS coefficients (no accidental autograd).
  • | for chains — readable in hot loops. Skip + (parallel-mix) — ASA analyzes bands separately, doesn't sum them.
  • Implement the pattern internally in apps/backend/dsp_utils.py. Do not pip install torchfx.

See the doc for the full call-site inventory in analyze_detection.py / analyze_core.py and the borrowed-pattern checklist.

https://claude.ai/code/session_01WJ4q6kzeEMscgnSa9gRqQk


Generated by Claude Code

Library evaluation of matteospanio/torchfx against ASA's per-band
filtering call sites. Conclusion: do not adopt — GPL v3 vs ASA's
MIT license is a one-way door, the shipped filter inventory is too
sparse to displace scipy, and Phase 1 bandpass cost is not on the
critical path. The torchfx |/+ operator pattern and the (tensor, fs,
device) carrier object are worth borrowing only if a future profile
shows per-band filtering is a top-three Phase 1 cost on long tracks.

https://claude.ai/code/session_01WJ4q6kzeEMscgnSa9gRqQk

@slittycode slittycode left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: APPROVE

Summary

Docs-only addition of docs/history/library-review-torchfx-2026-05-13.md — a library evaluation ruling out matteospanio/torchfx as a dependency. No code changes, no schema changes, no test impact. The GPL v3 / MIT incompatibility finding is correct and decisive on its own.

Findings

None. The verifiable claims hold:

  • Call-site inventory matches the code (analyze_detection.py:38, 97–120, 481, 554; analyze_core.py:894–903).
  • ASA is MIT (/LICENSE). GPL v3 linkage would force the combined work to GPL v3. Recommendation is correct.
  • Demucs GPU placement claim (analyze_transcription.py:497, 521) is accurate.
  • docs/history/ is the right location per CLAUDE.md ("completed plans and one-shot audits").

Test results

No tests required — docs only.

Phase boundary check

Clean. No code touched.


Generated by Claude Code

@slittycode
slittycode merged commit 6ac0a16 into main May 14, 2026
2 checks passed
@slittycode
slittycode deleted the claude/review-torchfx-inspiration-LpSZq branch May 14, 2026 00:53
slittycode added a commit that referenced this pull request May 14, 2026
…edBandpass (#44)

* docs(history): add torchfx library review

Library evaluation of matteospanio/torchfx against ASA's per-band
filtering call sites. Conclusion: do not adopt — GPL v3 vs ASA's
MIT license is a one-way door, the shipped filter inventory is too
sparse to displace scipy, and Phase 1 bandpass cost is not on the
critical path. The torchfx |/+ operator pattern and the (tensor, fs,
device) carrier object are worth borrowing only if a future profile
shows per-band filtering is a top-three Phase 1 cost on long tracks.

https://claude.ai/code/session_01WJ4q6kzeEMscgnSa9gRqQk

* refactor(backend): centralize per-band Butterworth filtering in BatchedBandpass

The torchfx audit (docs/history/library-review-torchfx-2026-05-13.md, PR #39)
ruled out adopting matteospanio/torchfx as a dependency (GPL v3 vs ASA's MIT)
but flagged the underlying pattern — a small filter primitive that batches
per-band work and gives a clean hook for future GPU acceleration — as worth
borrowing internally. This commit implements the minimum slice of that
pattern that earns its complexity today: PR 1 of the audit's plan.

Three scipy-Butterworth bandpass call sites in analyze_detection.py
(_bandpass_signal, the reverb per-band RT60 loop) were each re-implementing
the same butter(4, [lo, hi], output='sos') + sosfiltfilt sequence with
slightly different glue. They now share one BatchedBandpass instance per
sample rate (cached via functools.lru_cache(maxsize=4)) that memoises SOS
coefficients per (lo_hz, hi_hz). The 7-band transient-density loop in
analyze_per_band_transient_density and the Essentia BandPass call sites
are intentionally out of scope for this PR.

Output is bit-identical to the previous inline implementation. The
load-bearing test (test_filter_one_matches_inline_scipy_bit_for_bit) compares
the new class against a literal copy of the pre-refactor _bandpass_signal
arithmetic on a fixed-seed signal and asserts np.allclose(atol=1e-12,
rtol=1e-12). The full pre/post snapshot of analyze.py output on a synthetic
click-train fixture is zero-diff (including reverbDetail.perBandRt60), so
Phase 1 numbers do not move.

No JSON field changes, no schema changes, no new dependencies. The
BatchedBandpass(backend=...) argument is a forward-looking seam — only
"scipy" is implemented today; a future torch path would land here if
profiling shows per-band scipy filtering is hot.

Tests:
  - tests.test_dsp_utils.BatchedBandpassTests: 8/8 OK (incl. bit-identical
    parity for both filter_one and filter_many)
  - tests.test_analyze.ReverbDetailTests: 7/7 OK (unchanged)
  - discover -s tests: 551/551 OK

https://claude.ai/code/session_01WJ4q6kzeEMscgnSa9gRqQk

* refactor(backend): accept dtype kwarg on BatchedBandpass.filter_one

Addresses review feedback on PR #44: the original filter_one hardcoded
float32 while filter_many defaulted to float64, which would be a footgun
for the planned analyze_per_band_transient_density migration (PR 2 of
the audit). filter_one now accepts a keyword-only ``dtype`` argument,
making the public API symmetric with filter_many.

The default remains ``np.float32`` — bit-identicality with the pre-refactor
_bandpass_signal is preserved, and the existing reverb call site is
unchanged. The asymmetric defaults (float32 for filter_one, float64 for
filter_many) are intentional and now documented in a class-level note:
each default matches its primary use case verbatim, and callers can
override either at the call site.

Two new tests lock the contract:
  - test_filter_one_default_dtype_is_float32 — asserts default is float32
    (bit-identicality guarantee with _bandpass_signal)
  - test_filter_one_respects_dtype_override — asserts dtype=np.float64
    produces a float64 array (the forward-looking override path)

BatchedBandpassTests: 10/10 OK. ReverbDetailTests: 7/7 OK unchanged.

https://claude.ai/code/session_01WJ4q6kzeEMscgnSa9gRqQk

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants