Skip to main content

Modernized, from-scratch reimplementation of madmom's inference-relevant audio-DSP and beat/downbeat-tracking algorithms

Project description

madmom-infer

A from-scratch, modernized reimplementation of madmom's inference-relevant algorithms

PyPI License: BSD-2-Clause Python 3.9+


Why this exists

madmom is a well-regarded MIR (Music Information Retrieval) / audio-DSP research library out of CPJKU (Johannes Kepler University, Linz) and OFAI (Vienna). Its algorithms -- spectrogram feature extraction, beat and downbeat tracking, onset detection, tempo estimation, and more -- are still widely used and cited. But madmom's PyPI release is roughly 8 years stale, ships compiled Cython extensions, and is difficult or impossible to install cleanly on modern Python / numpy / scipy versions.

madmom-infer re-derives madmom's inference-relevant algorithms from scratch against current Python tooling. It is an independent reimplementation, not an official fork -- see NOTICE. It does not reuse or redistribute any of madmom's original source code; it reimplements the published algorithms.

Scope

This project targets madmom's inference code only:

  • Signal processing and feature extraction (framing, STFT, filterbanks, log-spectrograms)
  • Decoding algorithms (Viterbi-based HMM/DBN beat and downbeat tracking)
  • Onset/tempo/chord/key/note feature extraction (Phase 2, see below)

Out of scope, forever:

  • madmom.evaluation.* -- madmom's F-measure/precision-recall research evaluation metrics (~4447 lines). This is tooling for scoring MIR research output, not for inference, and is not part of this project's scope under any phase.
  • Training-only code. Madmom itself has essentially no gradient-based training code to port (its neural-net layers are forward-inference-only already).

Backend

  • numpy backend (default, required, and the only backend that exists today): the reference implementation. Verified bit-identical to original madmom via golden-fixture tests -- recordings of real madmom output, checked against this port's output. This is the same testing philosophy used by the sibling all-in-one-infer package's pure-Python NATTEN replacement.
  • torch backend: not implemented. It is a planned Phase 3 item (see docs/DESIGN.md for the design proposal) -- there is no import torch anywhere in this codebase yet, and no torch extra to install. If it ships, it's expected to help most at the spectrogram/STFT stage (batches trivially across frames); the Viterbi decoder is an inherently sequential, per-frame recursion, so GPU gains there would be marginal -- noted here so nobody oversells that speculative speedup in the meantime.

Roadmap

Phase 1 -- complete

Driven by the sibling all-in-one-infer package's needs. All of the original madmom code in this phase is pure numpy/scipy (no Cython), so most of it was a near-mechanical port, verified against madmom via golden fixtures:

  • Signal, FramedSignalProcessor (madmom_infer/audio/signal.py)

  • ShortTimeFourierTransformProcessor (madmom_infer/audio/stft.py)

  • Filterbank construction (madmom_infer/audio/filters.py)

  • FilteredSpectrogramProcessor, LogarithmicSpectrogramProcessor (madmom_infer/audio/spectrogram.py)

  • Processor / SequentialProcessor composition (madmom_infer/processors.py)

  • A numpy reimplementation of madmom's Cython Viterbi decoder (madmom/ml/hmm.pyx -> HiddenMarkovModel.viterbi()), the phase-1 centerpiece (madmom_infer/ml/hmm.py), plus the bar-length state-space and DBN downbeat tracker that consume it (madmom_infer/features/beats_hmm.py, madmom_infer/features/downbeats.py).

    This is feasible in pure numpy because the beat/downbeat state space is small (~11k-15k states per bar-length HMM), transitions are sparse (~1-2 incoming edges per state), and the recursion is log-domain -- well within numpy vectorization territory, no Cython/C/GPU required.

Every stage above has a golden-fixture test (tests/test_*.py, 84 tests total) proving bit-identical output against a real, compiled madmom 0.17.dev0 install -- except the one stage where bit-identity is bounded by BLAS library non-associativity rather than achievable in principle: the filterbank matrix-multiply (np.dot(spectrogram, filterbank)) rounds differently by a handful of float32 ULPs depending on which OpenBLAS build numpy resolves to. This is proven, not assumed -- tests/test_spectrogram.py's test_filtered_spectrogram_algorithm_is_exact_under_original_blas exports this port's own computed arrays and re-runs the same matmul through the original reference venv's numpy/BLAS build, reproducing the golden fixture with zero differing elements. Everything downstream of the STFT and filterbank-matrix stages (which ARE bit-identical) is verified to within 64 ULPs (worst case observed: 12, on the committed fixtures).

End-to-end acceptance: run against the sibling all-in-one-infer package (3 stems separated once, shared between two isolated environments -- one with real madmom, one with madmom-infer via a thin import madmom compatibility shim) on a 90-second real-music excerpt, harmonix-all model, CUDA: bpm, beats, downbeats, beat_positions, and segments (boundaries + labels) came out byte-for-byte IDENTICAL. The intermediate spectrogram .npy arrays differed by up to ~1778 float32 ULPs (max abs diff ~2.4e-7) on this longer, more complex real track -- the same proven BLAS-non-associativity source as above, confirmed to have zero effect on the final decoded output. A same-environment rerun (real madmom vs. itself) reproduced both the fields and the spectrogram bit-for-bit, confirming the pipeline itself is fully deterministic and isolating the spectrogram delta to the BLAS difference between environments.

Phase 2 -- NN runtime + RNNDownBeatProcessor end-to-end: complete

The weights-bundling question is resolved as: never bundle, always download at runtime (see "What this project will NEVER bundle" below). Phase 2 ships:

  • A forward-pass-only NN runtime (madmom_infer/ml/nn/{__init__,layers, activations}.py), porting exactly the layer/activation types the target ensemble needs: NeuralNetwork/NeuralNetworkEnsemble, FeedForwardLayer, RecurrentLayer, BidirectionalLayer, Gate/Cell/ LSTMLayer, and the linear/tanh/sigmoid/relu/elu/softmax activations. Everything in madmom/ml/nn/* is already forward-inference- only (no backward/train/fit/grad anywhere, confirmed by grep) and pure Python/NumPy (no Cython), so this was a near-mechanical port, same as Phase 1's spectrogram chain.
  • A restricted, class-allowlisted unpickler (madmom_infer/ml/nn/ unpickle.py) for madmom's own .pkl model files -- deliberately NOT a bare pickle.load, since unpickling is inherently code execution and a downloaded model file is a lower-trust artifact than this project's own source. Only the exact class/function paths the target models reference are allowed (see unpickle.py's mapping table); anything else raises loudly.
  • A runtime weights-download layer (madmom_infer/models.py): fetches madmom's DOWNBEATS_BLSTM ensemble (8 downbeats_blstm_[1-8].pkl files) from the official CPJKU/madmom_models GitHub repository over HTTPS, caches them under $XDG_CACHE_HOME/madmom_infer/models/ (never inside this project), and verifies each download's sha256 against a pinned known-good table before use.
  • RNNDownBeatProcessor (madmom_infer/features/downbeats.py): the multi-frame-size (1024/2048/4096) spectrogram + SpectrogramDifference pre-processing cascade, feeding an 8-network BLSTM ensemble, chained into the already-ported DBNDownBeatTrackingProcessor -- audio in, beat/ downbeat times out, matching real madmom exactly (see below).

Verification: unpickled-model structural digest (layer types, shapes, every weight/bias/recurrent/peephole-weight array's sha256, activation function names) matches real madmom's own unpickling exactly, across all 8 ensemble networks (tests/test_ml_nn.py). Activations match to within a documented ULP bound (up to 190 ULP observed, compounding Phase 1's proven BLAS non-associativity across dozens of np.dot calls per ensemble member) -- proven algorithm-exact, not just "close", by the same technique Phase 1 established: this project's own code, re-run under the original reference venv's numpy/BLAS build, reproduces real madmom's recorded activations AND decoded beat/downbeat times with zero differing elements (tests/test_downbeats_rnn.py::test_full_pipeline_is_exact_under_original_blas). Decoded beat/downbeat times are exact in every environment tested (the DBN decode is an integer-domain argmax, which absorbs float32-ULP-scale input noise). Onset/tempo/chord/key/note feature extraction beyond RNNDownBeatProcessor remains out of scope for now (see roadmap below).

Phase 3 (odds and ends, not started)

Remaining audio submodules (chroma, HPSS, cepstrogram) and two more small Cython units: features/beats_crf.pyx and audio/comb_filters.pyx. Also where a torch backend would land, if built (see docs/DESIGN.md for the design proposal) -- planned, not implemented; there is no torch dependency or extra in this project today.

What this project will NEVER bundle

madmom's own pretrained model weights (.pkl and similar files) are licensed CC BY-NC-SA 4.0 (non-commercial) by the original authors -- a separate, more restrictive license than madmom's BSD-2-Clause source code. This is a permanent policy, not a phase-2-only caveat: madmom-infer will never bundle, vendor, or redistribute any of madmom's own pretrained weights, in any phase, for any reason. See NOTICE for the full statement.

Instead (Phase 2, madmom_infer/models.py), weights are downloaded at runtime, on demand, directly from the official CPJKU/madmom_models GitHub repository, cached locally under $XDG_CACHE_HOME/madmom_infer/models/ (never inside this project's own package or git history), with sha256 verification against a pinned known-good table. The downloaded weight bytes remain CC BY-NC-SA 4.0 -- non-commercial use only -- regardless of madmom-infer's own BSD-2-Clause license, which covers only this project's source code, never the weights it fetches at runtime. See NOTICE and madmom_infer/models.py's module docstring for the full statement.

Install

pip install madmom-infer

Phase 1 -- the DSP feature-extraction pipeline and numpy Viterbi/DBN downbeat decoder -- and Phase 2 -- the NN runtime, restricted model unpickling, runtime weights download, and RNNDownBeatProcessor end-to-end -- are complete and golden-fixture verified; Phase 3 is not yet started. See the Roadmap above.

Attribution

madmom-infer reimplements algorithms originally published by CPJKU/madmom:

https://github.com/CPJKU/madmom

See LICENSE and NOTICE for full attribution and licensing details.

Development

This project uses uv:

uv sync
uv run python -c "import madmom_infer; print(madmom_infer.__version__)"
uv run pytest -v

The default pytest run above is fully offline (99 tests, network-marked tests deselected by pyproject.toml; this is what CI runs). To also exercise the network-dependent A/B tests against real, freshly-downloaded madmom weights, run uv run pytest -m network -v. See CLAUDE.md's "Phase-2 verification commands" for the full picture, including the reference-venv cross-BLAS proof.

License

BSD-2-Clause. See LICENSE. Note: this covers madmom-infer's source code only -- see "What this project will NEVER bundle" above regarding madmom's separately-licensed pretrained weights.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

madmom_infer-0.1.1.tar.gz (56.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

madmom_infer-0.1.1-py3-none-any.whl (68.7 kB view details)

Uploaded Python 3

File details

Details for the file madmom_infer-0.1.1.tar.gz.

File metadata

  • Download URL: madmom_infer-0.1.1.tar.gz
  • Upload date:
  • Size: 56.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for madmom_infer-0.1.1.tar.gz
Algorithm Hash digest
SHA256 f9d741d3a3ad18865d6772a86f83e6e943cfacd389b41306db2631ca11d98c95
MD5 3a70339953212ba136be8e57de8aae9b
BLAKE2b-256 0221cf505b4ec806ed4e4591ba954189af9e2c9b9eee03ccffe369c767769da4

See more details on using hashes here.

Provenance

The following attestation bundles were made for madmom_infer-0.1.1.tar.gz:

Publisher: publish.yml on openmirlab/madmom-infer

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file madmom_infer-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: madmom_infer-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 68.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for madmom_infer-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 fd66ef16a8e4d746dac9ccfed4e40e3b742b98494a92296153a51f0fd04bc141
MD5 024ef828beb5a3722f4ca8abb1e29e0c
BLAKE2b-256 d552b402586d42f8b2a37eac229a2154c111ed0e6f11fdb6923cb05112b049da

See more details on using hashes here.

Provenance

The following attestation bundles were made for madmom_infer-0.1.1-py3-none-any.whl:

Publisher: publish.yml on openmirlab/madmom-infer

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page