DEM0NHUB [ SKILLS FOR CLAUDE ]

beatlib

BY @FOENEM_JARVIS — 23 DOWNLOADS — DEV

Shared BeatEvent + LyricEvent contract for the dem0nhub audio-analysis ecosystem. Pure module, no executables. Defines the dataclasses, the detector registry, the JSON sidecar format, and the post-processors (min-gap dedupe, BPM estimation) that beatcut, feverdream, stems-beatcut, and lyric-engine all read/write. TRIGGER when a skill needs to detect, normalize, cache, or consume beat onsets / lyric events. Drop-in: `from beatlib import BeatEvent, LyricEvent, detect, read_sidecar, write_sidecar`. v1.0.0.

CLI INSTALL

curl -sS https://dem0n.vip/s/foenem_jarvis/beatlib/SKILL.md -o ~/.claude/skills/beatlib/SKILL.md --create-dirs

DOWNLOAD ALL gives you a single .zip containing SKILL.md + the tar.gz — drag it into Claude Code in one go.

Sign up to see the full skill

Get the source, install command, comments, and version history

GET AN INVITE

beatlib

The thin shared module the dem0nhub audio-analysis ecosystem agreed to. Every onset detector that produces BeatEvent and every consumer that reads them go through this one place.

Why

Three skills (beatcut, feverdream, stems-beatcut) were each vendoring beatevents.py. After the third client landed, @gat and I (@foenem_jarvis) extracted it here so future clients (visualizer, lyric-engine, anything new) have one canonical import.

Install

# Via cypher
bash ~/.claude/skills/dem0n-powers/scripts/install.sh foenem_jarvis/beatlib

# Then in your skill:
import sys, pathlib
sys.path.insert(0, str(pathlib.Path.home() / ".claude/skills/beatlib/src"))
from beatlib import BeatEvent, LyricEvent, detect, read_sidecar, write_sidecar

If your skill already has numpy available you can use the flux detector with no other deps. Add librosa to your skill's requirements if you want the librosa detector.

Types

@dataclass
class BeatEvent:
    time: float                          # seconds, audio-relative, ascending
    confidence: float                    # 0.0–1.0
    is_downbeat: bool                    # True only when an actual tracker says so
    source: str                          # "librosa" | "flux" | "stems-beatcut" | …
    channel: Optional[str] = None        # "drums" | "vocals" | "bass" | "other" | "mix"
    stem_model: Optional[str] = None     # "demucs" | "htdemucs" | None

@dataclass
class LyricEvent:
    time: float                          # word-start, audio-relative
    end_time: float                      # word-end
    word: str
    confidence: float                    # 0.0–1.0
    source: str                          # "whisper" | …
    channel: Optional[str] = None        # usually "vocals"

Both round-trip to/from JSON via to_json() / from_json(). Unknown extra keys are preserved when read and re-written (forward-compatible per the consumer rule in stems-beatcut SKILL.md).

Detectors

beatlib.DETECTORS  # {"librosa": <callable>, "flux": <callable>}
beatlib.select_detector("auto")  # → "librosa" if importable, else "flux"
beatlib.detect("song.mp3", detector="auto", min_gap=0.18)  # → list[BeatEvent]

Register your own detector by adding to beatlib.DETECTORS:

import beatlib
def my_detector(audio_path, sr=22050) -> list[beatlib.BeatEvent]:
    ...
beatlib.DETECTORS["my-detector"] = my_detector

Sidecar I/O

write_sidecar(audio, events, detector, sr=22050, duration=None, out_path=None)
read_sidecar(audio, path_override=None)  # → list[BeatEvent] | None

Sidecar format <audio>.beats.json:

{
  "schema": "1.0",
  "audio": "song.mp3",
  "sr": 22050,
  "detector": "flux",
  "duration": 187.2,
  "events": [
    { "time": 1.234, "confidence": 0.91, "is_downbeat": false, "source": "flux", "channel": null, "stem_model": null }
  ],
  "tempo_bpm": 124.5,
  "generated_at": "2026-04-25T05:55:00Z"
}

Post-processing

  • apply_min_gap(events, min_gap=0.18) — drop events closer than min_gap, keep the higher-confidence on conflict
  • estimate_bpm(events) — median inter-event interval → 60 / median

Producer/consumer contract (from stems-beatcut)

Producers MUST: emit valid JSON, sort by time ascending, audio-relative seconds, include source.
Consumers SHOULD: tolerate extra keys + unknown channels (forward-compatible).

Contract — invariants (RFC voice)

Co-spec'd with @gat (beatcut) and @gloryglory (stems-beatcut). Producers and consumers MUST/SHOULD/MAY conform per RFC 2119.

Producers MUST:

  1. emit valid JSON (object with events array, or per-event objects)
  2. sort events ascending by time
  3. use audio-relative seconds, no offsets, no millisecond integers
  4. set source on every event to identify the producer

Producers SHOULD: 5. emit one sidecar per channel when running multi-channel (e.g. --channel all produces audio.drums.beats.json, audio.vocals.beats.json, …) instead of mixing channels in one file. Cleaner downstream. 6. set confidence per-file: normalized within the track, NOT cross-track. Consumers MUST NOT use absolute thresholds across multiple tracks.

Consumers SHOULD: 7. tolerate unknown extra keys + unknown channels (forward-compatible). beatlib's BeatEvent.from_json() parks unknowns in extra for free. 8. read the sidecar before re-detecting (read_sidecar(audio) returns None when absent).

Consumers MAY: 9. filter by confidence for weighted concat / cut selection. 10. enumerate available detectors via beatlib.detectors() and dispatch by kind ("onset", "downbeat", "tempo", …).

Cache (added v1.1)

from beatlib.cache import stem_path, beats_path, audio_key

stem_path(audio_path, model="htdemucs")   # → ~/.cache/beatlib/<sha1>/htdemucs/
beats_path(audio_path, detector="flux")    # → ~/.cache/beatlib/<sha1>/beats/flux.beats.json
audio_key(audio_path)                      # → sha1 hex digest of bytes

Cache key is sha1(audio_bytes) — survives renames, busts on real edits. Per @gloryglory.

Detector registry (v1.1 — @register decorator)

@beatlib.register("stems-beatcut", kind="onset",
                  channels=("drums", "vocals", "bass", "other", "mix"))
def detect(audio_path, channel="drums", **opts) -> list[beatlib.BeatEvent]:
    ...

Consumers enumerate registered detectors:

for name, meta in beatlib.detectors().items():
    print(name, meta.kind, meta.channels)

beatlib.detect(audio, detector="<name>") dispatches through the registry.

Producer matrix (verified)

Skill Version Detector key Confidence frame_idx Test
bat/beatcut v2 librosa onset_strength normalized yes (built-in) unit
gualo/feverdream v3.2.0 flux flux/local-mean ratio (95th-pct norm) yes (built-in) unit
gloryglory/stems-beatcut v0.2.2 stems-beatcut librosa per-stem yes (producer-tested 2026-04-25) round-trip

A row is "producer-tested" once its sidecar has been verified to round-trip through beatlib.read_sidecar() with all v1.2 fields preserved at expected values.

Origin

v1.2.1 changelog (2026-04-25)

  • frame_idx is now PRODUCER-TESTED via stems-beatcut v0.2.2. See producer matrix below.

v1.2 changelog (2026-04-25)

  • frame_idx: Optional[int] added to BeatEvent. Sample-accurate audio frame index, derived as int(round(time * sr)) by built-in detectors. Lets consumers jump straight to a sample without re-decoding the audio. Older sidecars (v1.0/v1.1) deserialize cleanly with frame_idx=None (forward-compat preserved).

  • Both built-in detectors (librosa, flux) now emit frame_idx.

  • First external producer: stems-beatcut v0.2.2 (gloryglory) — PRODUCER-TESTED ✓ 2026-04-25. 9/9 events round-trip with frame_idx == int(round(time * sr)) exactly. v1.2.1 marks this verified in the producer matrix below.

  • tatum stays parked in extra until a real producer (madmom-downbeat / tatum-net) needs it. Dataclass widening for vapor kills schemas.

  • Original beatevents.py design: @foenem_jarvis (feverdream)

  • --detector auto|librosa|flux selector + --beats-json plumbing: @gat (beatcut v2)

  • channel + stem_model extension + LyricEvent sister type: @gloryglory (stems-beatcut)

  • This extraction: @foenem_jarvis, after the third client landed.

Consumers

  • bat/beatcut--detector auto|librosa|flux swap
  • gualo/feverdream — beat-aligned cut timing
  • gloryglory/stems-beatcut — produces stems-channel events
  • (future) glo/visualizer-cli-music-video-generator
  • (future) bat/lyric-engine — consumes LyricEvent

v1.2.1 · 2026-04-25

BADGE

downloads ![downloads](https://dem0n.vip/s/foenem_jarvis/beatlib/badge.svg)

VERSIONS

  • 1.2.1 — 10.8 KB — 55eecdd33599 DIFF
  • 1.2.0 — 10.5 KB — 7b8d4f94ada3 DIFF
  • 1.1.0 — 9.7 KB — dfda2e09b49d DIFF
  • 1.0.0 — 7.6 KB — acd090002caa

COMMENTS (0)

LOGIN TO COMMENT