Skip to main content
Glama
Profazia

beatlyzer-mcp

by Profazia
README.md
# beatlyzer

**Audio analysis that machines (and humans) can read.**

Point `beatlyzer` at an `.mp3` (or `.wav`, `.flac`, `.ogg`, `.m4a`, …) and it produces:

- a **structured, LLM-friendly JSON** description of the track,
- a **rich terminal summary**,
- an **annotated multi-panel visualization** (PNG) marking **beat drops**,
  **volume surges**, **high tones**, brightness, structure and the spectrogram,
- an optional **Markdown report**.

It's designed so a vision- or text-capable AI model can "understand" a song:
what it sounds like, how it's structured, where the energy peaks, and exactly
*when* the interesting moments happen.

---

## Example

`beatlyzer demo.wav` produces this four-panel analysis:

![beatlyzer analysis of demo.wav](demo.beatlyzer.png)

Alongside the PNG it writes a machine-readable
[`demo.beatlyzer.json`](demo.beatlyzer.json) and a
[`demo.beatlyzer.md`](demo.beatlyzer.md) report — both included here. Regenerate
the demo input yourself with `python scripts/make_sample.py demo.wav`.

---

## What it detects

| Signal | How | Output |
| --- | --- | --- |
| **Tempo & beats** | librosa beat tracking | BPM + beat grid |
| **Key** | Krumhansl-Schmuckler chroma correlation | e.g. `F minor` |
| **Beat drops** | sharp rises into loud, **bass-heavy** passages, snapped to beats | timeline events |
| **Volume surges** | crescendos / hits (loudness jumps that aren't drops) | timeline events |
| **High tones** | spectral-centroid + treble-energy peaks | timeline events |
| **Loudness & dynamics** | RMS in dB relative to peak | avg / peak / dynamic range |
| **Brightness** | spectral centroid | dark ↔ airy |
| **Structure** | agglomerative clustering of timbre+harmony | intro → build → drop → outro |

---

## Install

Requires Python 3.9+ (3.10+ for the MCP server). For **mp3/m4a/aac** decoding
you need **ffmpeg** on your PATH (`sudo dnf install ffmpeg` /
`brew install ffmpeg` / `apt install ffmpeg`).

### As a global CLI tool — recommended, no venv to manage

The cleanest way to get `beatlyzer` (and `beatlyzer-mcp`) available *everywhere*
is [pipx](https://pipx.pypa.io) or [uv](https://docs.astral.sh/uv/), which each
install the tool into their own isolated environment and put the commands on
your PATH — you never create or activate a virtualenv yourself:

```bash
# pipx
pipx install 'git+https://github.com/Profazia/beatlyzer.git'
pipx inject beatlyzer mcp          # add the MCP server extra

# or uv
uv tool install 'beatlyzer[mcp] @ git+https://github.com/Profazia/beatlyzer.git'

# run once without installing anything permanent:
uvx --from 'git+https://github.com/Profazia/beatlyzer.git' beatlyzer song.mp3
```

> **Note:** `librosa`/`numba` may lag the newest Python release. If a build
> fails, pin the interpreter: `pipx install --python python3.12 ...`.

### From a clone (development)

```bash
pip install -e ".[dev,mcp]"       # editable install with test + MCP deps
```

This installs the `beatlyzer` and `beatlyzer-mcp` commands.

---

## Usage

```bash
# analyze a file — writes <name>.beatlyzer.{png,json,md} next to it
beatlyzer song.mp3

# choose an output directory and open the image when done
beatlyzer track.wav -o out/ --open

# only the machine-readable JSON, printed to stdout, no files, no chatter
beatlyzer mix.flac --format json --print-json --quiet
```

Don't have a file handy? Generate a demo track:

```bash
python scripts/make_sample.py sample.wav
beatlyzer sample.wav --open
```

### Options

```
-o, --output-dir DIR     Where to write outputs (default: next to the input)
-f, --format CHOICE      all | png | json | md | none   (default: all)
    --stem NAME          Base name for output files (default: input name)
    --sr INT             Analysis sample rate (default: 22050)
    --dpi INT            PNG resolution (default: 140)
    --open               Open the PNG when finished
    --quiet              Suppress the terminal summary
    --print-json         Print the JSON summary to stdout
-V, --version            Show version
```

Run as a module too: `python -m beatlyzer song.mp3`.

---

## The AI-readable JSON

`<name>.beatlyzer.json` follows the `beatlyzer.analysis/v1` schema. Feed it
straight to an LLM — it's self-describing (see the `ai_notes` field):

```jsonc
{
  "schema": "beatlyzer.analysis/v1",
  "metadata": { "duration_hms": "0:24", "tempo_bpm": 128.0, "estimated_key": "A minor", ... },
  "loudness":  { "average_db": -18.4, "dynamic_range_db": 31.2, "description": "wide dynamics" },
  "brightness":{ "average_centroid_hz": 2680.0, "description": "balanced" },
  "sections":  [ { "label": "intro", "start_hms": "0:00", "energy_level": "low" }, ... ],
  "events": [
    { "time_hms": "0:09", "type": "beat_drop",  "strength_0_1": 1.0, "bass_share": 0.71,
      "description": "Beat drop at 0:09 — energy surges into a loud, bass-heavy section." },
    { "time_hms": "0:17", "type": "high_tone",  "strength_0_1": 0.93, "centroid_hz": 6011.0, ... }
  ],
  "energy_profile": [ { "t": 0.0, "energy": 0.05, "loudness_db": -34.1, "brightness": 0.2 }, ... ],
  "summary_text": "This 0:24 track is a high-energy piece at 128 BPM in A minor. ...",
  "ai_notes": "Times are seconds from the start. 'loudness' is dB relative to the track's peak ..."
}
```

## The visualization

`<name>.beatlyzer.png` is a four-panel figure sharing one time axis:

1. **Waveform** with shaded structural sections and a faint beat grid.
2. **Loudness (dB)** with **beat drops** (red) and **volume surges** (orange).
3. **Brightness** (spectral centroid) with **high-tone** peaks (purple).
4. **Mel spectrogram** with drop lines overlaid.

---

## Use it as an MCP server

beatlyzer ships an [MCP](https://modelcontextprotocol.io) server so AI agents —
Claude Code, Claude Desktop, Cursor, etc. — can analyze audio directly. It runs
over stdio and exposes three tools:

| Tool | Returns |
| --- | --- |
| `analyze_audio(file_path, sample_rate=22050)` | full structured JSON summary |
| `describe_audio(file_path)` | a short prose description |
| `visualize_audio(file_path, output_path=None, dpi=140)` | the annotated PNG, as an image the model can see |

Make sure the `mcp` extra is installed (`pipx inject beatlyzer mcp`, or
`pip install 'beatlyzer[mcp]'`), then register the server.

**Claude Code:**

```bash
claude mcp add beatlyzer beatlyzer-mcp
```

**Claude Desktop / any MCP client** (`claude_desktop_config.json` or equivalent):

```json
{
  "mcpServers": {
    "beatlyzer": {
      "command": "beatlyzer-mcp"
    }
  }
}
```

If `beatlyzer-mcp` isn't on the client's PATH, use the absolute path (e.g.
`~/.local/bin/beatlyzer-mcp`, or the one printed by `pipx list`). A ready-made
snippet lives in [`examples/mcp-config.json`](examples/mcp-config.json).

Then just ask the model things like *"analyze ~/Music/track.mp3 and tell me
where the beat drops are"* or *"visualize this song."*

---

## As a library

```python
from beatlyzer import analyze_file
from beatlyzer.report import build_result_dict

result = analyze_file("song.mp3")
print(result.summary_text)
print([e.time for e in result.drops])
doc = build_result_dict(result)   # the JSON-ready dict
```

---

## How the detection works (short version)

Every detector smooths a feature track, measures how it **transitions**
(mean energy *after* a moment minus mean *before*), then keeps prominent,
well-separated peaks and snaps them to the nearest beat:

- **Drops** blend overall RMS with sub-250 Hz **bass** energy and additionally
  require the landing passage to be genuinely loud — a build-up that fizzles
  isn't a drop.
- **Surges** track loudness jumps and are de-duplicated against drops.
- **High tones** combine the spectral centroid with the >4 kHz energy share.

These are transparent heuristics, not a trained model — fast, dependency-light,
and explainable. Tune thresholds in `src/beatlyzer/events.py`.

## Development

```bash
pip install -e ".[dev]"
pytest
```

## License

MIT — see [LICENSE](LICENSE).