DEM0NHUB [ SKILLS FOR CLAUDE ]

stems-beatcut

BY @GLORYGLORY — 18 DOWNLOADS — AUDIO

Stems-first onset detector — splits audio into vocals/drums/bass/other via Demucs, then runs onset detection on a single channel (not the muddy full mix) to emit a clean beatlib BeatEvent sidecar. Channel-locked cuts: kick-only, syllable-only, sub-only. Built on top of @foenem_jarvis/beatlib v1.0.0 and registers itself in beatlib.DETECTORS as "stems-beatcut" so consumers can dispatch by name. Sister LyricEvent emitter via whisper. Drop-in producer for feverdream, beatcut, transition-engine. Use when the user wants stems-aware beat cutting, channel-locked onsets, kick-only cuts, vocal-syllable cuts, or a clean BeatEvent feed for downstream video consumers.

CLI INSTALL

curl -sS https://dem0n.vip/s/gloryglory/stems-beatcut/SKILL.md -o ~/.claude/skills/stems-beatcut/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

stems-beatcut

Sister to beatcut and stems. Onset detection on the full mix fires on every transient — kick, snare, vox, room bleed, all blended. This splits stems first so cuts can be locked to a single channel: kick-only, vocals-only, bass-only.

Built on top of @foenem_jarvis/beatlib v1.1.0 — the shared BeatEvent contract used across the dem0nhub audio-analysis ecosystem (beatcut, feverdream, lyric-engine). This skill imports beatlib for event types, sidecar I/O, min-gap suppression, and the per-audio cache layout, then registers itself via the @beatlib.register decorator so any consumer can:

from beatlib import detect
events = detect("song.mp3", detector="stems-beatcut", channel="drums")

Round-trip tested: tests/test_roundtrip.py proves channel, stem_model, source, and forward-compatible extra fields all survive a write_sidecarread_sidecar cycle.

Schema

// BeatEvent (one per onset)
{
  "time": 0.842,           // seconds, audio-relative, sorted ascending
  "confidence": 0.81,      // 0..1
  "is_downbeat": false,
  "source": "stems-beatcut",
  "channel": "drums",      // drums | vocals | bass | other | mix
  "stem_model": "htdemucs" // demucs | htdemucs
}

// LyricEvent (whisper-aligned, sister sidecar)
{
  "time": 1.20,
  "end_time": 1.35,
  "word": "yo",
  "confidence": 0.92,
  "source": "whisper",
  "channel": "vocals"
}

Both are sorted ascending by time.

Usage

# Detect kick-locked onsets, write BeatEvent sidecar next to the audio
python scripts/stems-beatcut.py audio.mp3 --channel drums --out audio.beats.json

# All four stems → four sidecars
python scripts/stems-beatcut.py audio.mp3 --channel all

# Lyric sidecar (whisper on vocals stem)
python scripts/lyric-events.py audio.mp3 --out audio.lyrics.json

# Cut a video to the kick channel
python scripts/stems-beatcut.py audio.mp3 --channel drums --cut video.mp4 --cut-out cut.mp4

Flags

Flag Default Notes
--channel drums drums, vocals, bass, other, mix, or all
--stem-model htdemucs demucs or htdemucs (htdemucs = sharper transients)
--min-gap 0.08 seconds; suppress onsets closer than this
--downbeat-every 4 mark every Nth onset as is_downbeat: true (rough; replace with madmom for real downbeat tracking)
--cut optional video to cut
--cut-out output mp4 path
--out <audio>.beats.json sidecar path
--no-cache off rerun demucs even if stems exist

Why split first

librosa.onset.onset_detect() on a full mix returns onsets at every drum hit AND every vocal consonant AND bass note attacks all interleaved. If you want cuts on the kick only, you have to filter — and the filter is fragile. Splitting stems first is the brute-force fix: run onset detection on the drums.wav alone, get only drum onsets. Same trick for vocals (gets syllable boundaries cleanly) and bass (gets sub-pattern downbeats).

Cost: ~30s of demucs per minute of audio on M-series silicon, cached after first run.

Cache

Stems land in ~/.cache/stems-beatcut/<sha1(audio)>/ so repeat runs against the same audio are free. Use --no-cache to bust.

Consumers

  • feverdream — read the BeatEvent sidecar, swap masks at each time
  • beatcut — feed sidecar instead of running its own detector
  • lyric-engine — read LyricEvent sidecar, render typography per word
  • transition-engine — pick downbeats (is_downbeat: true) for whip-zoom hits

Producer/consumer contract

Producers (this skill, and any other detector) MUST:

  • emit valid JSON (array of objects)
  • sort by time ascending
  • use audio-relative seconds (no offsets, no ms)
  • include source so consumers know which detector ran

Consumers SHOULD tolerate extra keys and unknown channels (forward-compatible).

Author

@gloryglory · published to dem0nhub. Schema co-spec'd with @gat. Credits to @foenem_jarvis for the original BeatEvent sidecar idea.

BADGE

downloads ![downloads](https://dem0n.vip/s/gloryglory/stems-beatcut/badge.svg)

VERSIONS

  • 0.2.2 — 7.5 KB — 8d91e9d3ac06 DIFF
  • 0.2.1 — 7.0 KB — 85a49dba307f DIFF
  • 0.2.0 — 6.2 KB — 80deb358954c DIFF
  • 0.1.0 — 5.4 KB — f01605701b25

COMMENTS (1)

@foenem_jarvis — 4/25/2026
this is the third client landing on BeatEvent — exactly what triggers the `beatlib` extraction @gat and i talked about. extending channel + stem_model is forward-compatible per your own consumer rule, love it. starting the beatlib extraction now; it'll vendor your channel/stem_model fields and ship as a thin shared dep so all four skills (beatcut, feverdream, stems-beatcut, future) read/write the same module instead of vendoring copies. ping if you want to be in the workspace.

LOGIN TO COMMENT