Metadata-Version: 2.4
Name: metricon
Version: 0.0.2
Summary: Beats, downbeats and bars from neural logits — a fast state-space decoder.
Author-email: auvux <pkiers.1983@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/auvux/metricon
Project-URL: Repository, https://github.com/auvux/metricon
Project-URL: Issues, https://github.com/auvux/metricon/issues
Keywords: beat tracking,downbeat,meter,tempo,viterbi,hmm,mir
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
Classifier: Topic :: Scientific/Engineering
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.21
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Dynamic: license-file

# metricon

**Beats, downbeats and bars from neural logits — a fast state-space decoder.**

`metricon` is the *decoder* half of a beat-tracking pipeline. You bring per-frame
beat/downbeat **logits** from any model (e.g. [Beat This!](https://github.com/CPJKU/beat_this),
an RNN/TCN, your own); metricon turns them into metrical structure — beats,
downbeats, tempo and meter — with a state-space HMM decoded by Viterbi. No audio,
no neural net, no `torch`: just `numpy`.

```python
import metricon

beats = metricon.track_beats(beat_logits, fps=50)
print(beats.times)            # numpy array of beat times (seconds)
beats.to_tsv("beats.tsv")
```

From probabilities instead of logits:

```python
act = metricon.Activations.from_activations(beat_prob, fps=50)
beats = metricon.track_beats(act)
```

Downbeats, bars and meter (needs downbeat logits):

```python
# one meter for the whole piece (the best-scoring of the candidates)
bars = metricon.track_downbeats(beat_logits, downbeat_logits, beats_per_bar=(3, 4))

# or let the time signature change within the piece
bars = metricon.track_bars(beat_logits, downbeat_logits, beats_per_bar=(3, 4, 6))
print(bars.times)            # beat times (seconds)
print(bars.numbers)          # position in the bar, 1 = downbeat
print(bars.downbeat_times)   # just the downbeats
```

Constrain the path through events you already know (the rest are filled in) —
beats for `track_beats`, beats and/or downbeats for the bar trackers:

```python
beats = metricon.track_beats(beat_logits, fps=50, anchors=[1.02, 2.05, 3.07])

# force bar lines without pinning the meter — the decoder still picks 3/4 vs 4/4
bars = metricon.track_bars(beat_logits, downbeat_logits,
                           downbeat_anchors=[0.51, 2.49, 4.47])
```

See [`docs/api.md`](docs/api.md) for the full API.

## Why

Built from the published Krebs/Böck/Widmer equations (CC BY 4.0 — see
`docs/algorithm.md`): the efficient bar-pointer DBN with a fast, low-memory
decoder that comfortably handles long, high-fps tracks.

## Status

Beats, downbeats, bars and within-song meter changes all work today, decoded by
the compiled float32 kernel, plus "fill-in" anchored decoding from known beats
and downbeats.

## Roadmap

- **Arbitrary beat-in-bar anchors.** Beat and downbeat anchors work today (force
  a beat, or a downbeat / bar line, at a known time). Forcing a *specific*
  beat-in-bar position — e.g. "this is beat 3 of the bar" — needs per-state
  masking in the decoder rather than the current per-column masking, since the
  beat-in-bar lives in the state, not the emission column. Planned.

## License

MIT. See `NOTICE` for algorithm citations.
