← Discover MCPs and Agents
s
MCPDeveloper toolsGitHub

subverselab-sampler

YouTube or local audio to a sliced sample pack, with a waveform UI and a chop bench that writes into an Ableton project.

Links

README

From the repo.

SubverseLab Sampler

Builds a sample pack from a YouTube link (or a local audio/video file). It downloads the audio, runs tempo and transient analysis, and writes WAV slices plus a manifest.json according to the chosen chop mode.

Two interfaces, both driving the same engine (engine/): a CLI (sampler.py) and a waveform UI (ui/server.py).

Usage

cd ~/Desktop/SubverseLab-Launchpad/01_AI_Tools/subverselab-sampler

# simplest form: transient (hit) slices
./sample "https://www.youtube.com/watch?v=..."

# only 1:12 - 1:44, all four modes
./sample "https://youtu.be/..." --from 1:12 --to 1:44 --mode all

# if you know the tempo, give it by hand for bars mode (detection is a guess)
./sample "https://youtu.be/..." --mode bars --bpm 174 --bars 2

# a local file works too
./sample ~/Desktop/recording.wav --mode transient --normalize

Output goes by default to 04_Sample_Packs/<video-title>_<video-id>/.

04_Sample_Packs/nina-simone-sinnerman_abc123/
  _source.wav          <- trimmed, decoded full source (44.1k stereo)
  manifest.json        <- analysis + each slice's time, length and peak
  transient/001_transient_0m01.93s.wav
  bars/001_bars_0m11.87s.wav
  ...

The waveform UI

python3 ui/server.py     # http://127.0.0.1:8787

The server binds to 127.0.0.1 only; it is not exposed.

It has the one thing the CLI does not: correcting slice boundaries by hand. The mode produces slices, you fix them on the waveform, and export from there.

  • Drag on empty space = new slice · drag a slice = move it · drag its edge = resize
  • Two adjacent slices share one split point: pulling it moves both sides. shift+drag breaks the link and moves each side separately.
  • alt+click = delete · click = select and play · space = play/stop
  • Wheel = zoom, shift+wheel = scroll; the strip on top shows the whole file and the visible range
  • "Visible range only" = generate slices only within the on-screen time range

The manifest.json written from the UI records how the slices were actually produced: if you passed --bpm by hand, the manifest writes that grid, not librosa's estimate.

Also registered in .claude/launch.json as sampler-ui.

Live project layer (liveproj.py)

Live's Python API CANNOT open a project. This layer makes opening, reading state and quitting safe — and, most importantly, verifies from Live's own log whether the open ACTUALLY succeeded.

python3 liveproj.py status
python3 liveproj.py open bench.als --yes
python3 liveproj.py quit

open output:

result         : OPENED
  window       : BENCH_experiments
  log          : loaded=True repaired=0 corrupt=0

If there is unsaved work, Live's own dialog asks; this layer discards nothing automatically. Because a Remote Script can grow the log by megabytes a minute (768 MB has been seen), the search walks backwards in chunks.

Agent — the layer that tries techniques and has them scored (agent.py)

Division of labour:

layerwhat it does
choppercuts with a fixed technique; it knows what to cut, it does not experiment
agentscores techniques by trying them, writes the experiments into the project
yourate the experiments 0–10

An experiment = one technique tried on one recording. Each experiment is written into the project in its own 32-bar block, on its own tracks, with the technique name dropped on a locator.

python3 agent.py run recording.wav --template p.als --record "recording.wav" \
    --record-path /path/recording.wav --out experiments.als --bpm 103
#   warp_2bar    4 proposals · mean internal score 79.0 · id 63369f3d
#   warp_4bar    4 proposals · mean internal score 74.4 · id 851c7ce5
#   oneshot      4 proposals · mean internal score 83.8 · id 665eb903

python3 agent.py rate 63369f3d 8 --note "2 bar is closest to me"
python3 agent.py report

Two signals accumulate: your explicit rating, and what you did in the project (kept / moved / deleted, via bench.py --diff). The ledger is _ledger/experiments.jsonl.

If a technique cannot produce a candidate, the experiment is not written — the gap is not filled with a guess.

The bench — a chop that writes into the project (bench.py)

It writes no WAVs. It writes the proposals directly into an Ableton project, each candidate in its own 32-bar block on its own tracks. The clips point at the recording's original file; the cut is the clip's region — so in Live you move it, stretch it, delete it.

python3 plan.py recording.wav --bpm 103 --count 10 --json > plan.json
python3 bench.py --template project.als --record "recording.wav" --plan plan.json --out bench.als

You work on the bench, save, and then:

python3 bench.py --diff bench.proposal.json what_you_saved.als
# {"kept": 5, "moved": 2, "deleted": 3, "total": 10}

Every decision carries the proposal's score. As these accumulate, "does a high score actually survive" becomes measurable — that is the learning signal.

One tempo. A project carries a single BPM; section-level tempo cannot be written from an extension in Live (there is no automation API in the SDK, and rootNote/scaleName are read-only). The measured real BPM and key are written into the locator name instead.

Limit 0 — the output must be INSIDE THE PROJECT FOLDER. Live resolves samples by relative path first; if the set is written to another folder the audio appears offline even when the absolute path exists on disk. Measured: 15 sounds offline outside the project, zero inside it. bench.py refuses before writing rather than producing that.

New-recording support. If the recording does not appear in the template, the bench takes any clip as a skeleton and rebinds it to the new recording from scratch: file path, size, frame count, sample rate and warp map are rebuilt. The warp map is two markers under a constant-tempo assumption (0 s → 0 beats, duration → duration*bpm/60); if the recording's tempo is not actually constant it is fixed by hand in Live — that is the only thing guessed here. Verified: a set written with an mp3 that appears in no project opened in Live with 0 corruption, 0 repairs and 0 unopenable files.

Limit 2: headless verification shows the structure is correct (clip, track and locator counts and positions, by writing and reading back). It does not show that Live opens the file. That needs opening it by hand once.

Plan mode — a scored proposal table

python3 plan.py recording.wav --bpm 103 --count 20 --mix warp=60,oneshot=25,drum=15
python3 plan.py recording.wav --count 12 --write        # also write the WAVs
python3 plan.py recording.wav --json                    # JSON instead of a table

It evaluates the recording in three modes, scores the candidates, distributes them by percentage, and shows each proposal on one line:

  # MODE     SLICE                WHAT WAS DONE                    INTENDED RESULT                    SCORE
  1 warp     0:19.84-0:24.50      warped to 103 BPM, 2 bars        carries the track end to end       80.5
  9 oneshot  0:10.52-0:11.22      cut at transient, 700 ms         clean single hit - layer off-loop  94.9
 13 drum     1:53.17-1:53.45      kick band 4.4x the average       Drum Rack C1 - kick layer          78.3

What the score is made of (components are written out individually in plan.json):

ModeComponents
warplevel · RMS flatness (loop evenness) · chroma stability (staying in one harmony) · grid fit
oneshotattack strength · silence before the attack · decay time to −30 dB · peak level
drumthe above + band emphasis: the ratio of the candidate's kick/snare/hat band share to THE RECORDING'S OWN average

Band classification does not look at the raw share — the snare band is the widest, so doing that made everything a snare. Instead it measures "how much more bass- or treble-weighted is this hit than the record in general", and a candidate that does not clear a 1.3× threshold is not counted as a drum. A candidate pinned to the upper limit that never decays is not counted as a one-shot either.

If a mode cannot produce the requested number of candidates, the shortfall is not filled with guesses; how many it could produce is printed under the table.

Chop modes

ModeWhat it doesWhat for
transientmakes each attack a slice, running to the next attackdrum/percussion one-shots
barsequal N-bar loops on the detected beat gridpulling melodic loops
fixedfixed-length slices (--seconds)coarse scanning
silencesplits on silence (--top-db)speech, vocals, section splitting
gapsthe ranges where the vocal is silent (needs --vocal-ref)intro + violin answers + instrumental sections

Modes can be combined with commas, e.g. --mode transient,bars; --mode all runs all four. Each mode writes into its own subfolder.

gaps mode — the vocal-free ranges

If you separate the vocal with Ableton's stem splitter and pass the stem as a reference, the tool extracts the recording's choppable instrumental regions directly: the intro, the answer gaps between vocal phrases, and the instrumental sections.

./sample "no_vocals.wav" --vocal-ref "vocals.wav" --mode gaps --min-len 4.0

Vocal activity is decided by the vocal/instrumental ratio, not an absolute threshold (default: the vocal is active when it is 10 dB above the instrumental). An absolute threshold mistook separation bleed for vocal and reported one recording's instrumental share as 0%.

Example output (Müslüm Gürses – Bir Bilebilsen, assuming 103 BPM):

   0.0 →  54.7 s   54.7 s  ≈23.5 bars   <- intro
  76.4 →  87.1 s   10.7 s  ≈ 4.6 bars   <- answer
 119.7 → 157.3 s   37.6 s  ≈16.1 bars   <- instrumental section
 179.3 → 189.7 s   10.4 s  ≈ 4.5 bars   <- answer
 227.4 → 254.1 s   26.7 s  ≈11.5 bars   <- outro

Important parameters

FlagDefaultNote
--from / --tonone83, 1:23, 1:23.5, 01:02:03
--min-len0.08shortest slice. In transient mode it also filters double triggers
--max-lenunlimitedupper bound on a transient slice
--tail0.0tail appended to the end of a slice
--bars / --beats-per-bar2 / 4bars mode
--bpm / --grid-offsetnonemanual grid instead of detection. The grid starts at the first transient by default
--vocal-refnoneseparated vocal stem for gaps mode
--seconds2.0fixed mode
--top-db30silence mode threshold. Lowering it gives fewer/longer slices
--fade5 msanti-click in/out fade
--normalize [DBFS]offper-slice peak normalise, default target -1.0
--bit-depth2416 / 24 / 32
--max-slices200upper bound per mode
--analyze-only—writes nothing, just prints the analysis

Things you need to know (measured, not guessed)

  • Tempo detection is a guess. It uses librosa.beat.beat_track and there is no downbeat detection — so a slice boundary is a beat boundary, not necessarily bar 1. If the tempo comes out wrong, override it with --bpm.
  • In a synthetic test, detection returned 80.75 BPM on a 120 BPM file (because of the silent gaps). With --bpm 120 --grid-offset 0 the slices came out at exactly 4.000 s.
  • In transient mode, onset_detect(backtrack=True) can fire twice on a single hit; --min-len is the distance that filters it.
  • Slices below -60 dBFS are not written and are counted in manifest.json as skipped_silent. Slice numbers can therefore have gaps, so that the numbering stays aligned with the timeline.
  • engine/compat.py applies a shim: the installed librosa 0.10.1 calls scipy.signal.hann, which newer scipy moved to scipy.signal.windows. Because the other tools on this machine depend on the same versions, librosa was not upgraded and the shim was preferred.

Requirements

All installed and verified on this machine: yt-dlp (>= 2026.08.19 — older versions get 403 from YouTube), ffmpeg, python3 + librosa, soundfile, numpy, scipy.

Layout

sampler.py        CLI (slicing)
plan.py           scored proposal table
bench.py          the project-writing bench + learning diff
sample            CLI shortcut
engine/           shared engine
  fetch.py        yt-dlp + ffmpeg -> 44.1k stereo WAV
  analyze.py      tempo, transients, silence + --bpm override
  chop.py         four modes -> slice boundaries
  write.py        WAV writing, fades, normalise, manifest
  peaks.py        min/max envelope for the waveform (optional window)
  score.py        warp/one-shot/drum candidate scoring
  compat.py       librosa/scipy shim
ui/
  server.py       stdlib HTTP server (no extra dependencies)
  index.html      single-file UI (no external JS libraries)

Collected info

  • ★ 0 stars
  • Language: Python
  • Source updated: 9/18/2026

Config for your environment

Replace {MCP_ENDPOINT_URL} with this MCP’s endpoint URL (from its repo or docs above). No API key — you connect directly.

Tool

OS

Config file: ~/.cursor/mcp.json

{
  "mcpServers": {
    "mcp-server": {
      "url": "{MCP_ENDPOINT_URL}"
    }
  }
}

Paste into mcpServers in the config file. Restart Cursor after saving.

If this MCP is also published on mcpchannel.ai, you can subscribe from Browse and use the gateway config there instead.