Install
openclaw skills install audiollaConnect to a user-deployed audiolla server to perform stem separation, mastering, MIR analysis, DSP transforms, and loudness normalization on audio files.
openclaw skills install audiollaHTTP + MCP client for an audiolla server that the user has already deployed. This skill talks to a running audiolla instance — it does not stand one up, does not download model weights manually, and does not modify the server config on its own initiative.
For installation and setup, see references/setup.md.
The user has audiolla running and asks you to:
transparent or loud)export AUDIOLLA_URL=http://localhost:8000
export AUDIOLLA_TOKEN=<the-token-the-user-gives-you> # only if auth is enabled
If AUDIOLLA_URL is not set, ask the user — do not search the workspace for it. Same for AUDIOLLA_TOKEN: only accept it from the env var the user set or from the user directly. Never read it from docker-compose.yml, .env, or any other repo file on your own initiative.
Verify: curl $AUDIOLLA_URL/healthz → {"ok": true, "device": "...", "engines": [...]}. /healthz is always unauthenticated regardless of AUDIOLLA_AUTH_TOKEN.
Auth is optional. If the server has AUDIOLLA_AUTH_TOKEN set, every endpoint except /healthz requires Authorization: Bearer $AUDIOLLA_TOKEN. Without it you get 401. Always pass the token if the user gave you one; don't assume the server has auth off.
GET reads state, POST processes audio, PUT uploads to the staging area, DELETE removes things. Audio comes in via multipart file form fields. Output is either audio bytes (with Content-Disposition: attachment) or JSON.
Every error response:
{"detail": "description of what went wrong"}
Status codes follow REST conventions:
200 — success400 — bad input (unknown engine, invalid features, bad operations JSON, etc.)401 — missing/invalid bearer token (only when auth is enabled)404 — unknown engine slug, unknown file path413 — upload exceeded AUDIOLLA_MAX_UPLOAD_BYTES (default 200 MB)415 — unsupported output_format500 — server error (engine failed internally, etc.)| Slug | What it does | Notes |
|---|---|---|
htdemucs | 4-stem separation | drums, bass, other, vocals |
htdemucs_ft | 4-stem fine-tuned | CUDA-only at usable speed — flagged cuda_only, the server rejects it with 400 on CPU |
htdemucs_6s | 6-stem separation | adds guitar + piano (experimental, CPU OK but slow) |
mdx_extra | 4-stem MDX-Net | drums, bass, other, vocals — strong vocal isolation |
matchering | Reference-based mastering | GPL v3 |
pedalboard-chain | Preset DSP mastering chain | presets: transparent, loud — GPL v3 |
librosa-analyze | MIR analysis + loudness | also backs the /v1/audio/loudness endpoint |
sox-transform | SoX DSP chain | gain, EQ, compand, reverb, pitch, tempo, rate, channels, trim, pad |
Engines lazy-load on first use and auto-unload after AUDIOLLA_ENGINE_TTL seconds of idle (default 600s). Demucs weights prefetch into /data/torch_cache/ at container start so the first separation request doesn't pay the cold-download cost.
Use GET /v1/engines to confirm what's actually configured on the running server (operators can restrict via AUDIOLLA_ENABLED_ENGINES).
Any endpoint that returns audio accepts -F "output_format=<fmt>". Supported: wav (default), mp3, flac, opus, aac, pcm.
# Liveness — no auth required
curl $AUDIOLLA_URL/healthz
# {"ok": true, "device": "cpu", "engines": ["htdemucs", "matchering", ...]}
# Configured engines + capabilities
curl -H "Authorization: Bearer $AUDIOLLA_TOKEN" $AUDIOLLA_URL/v1/engines
# Engines currently loaded in memory (and how idle)
curl -H "Authorization: Bearer $AUDIOLLA_TOKEN" $AUDIOLLA_URL/api/ps
# Evict one engine
curl -X DELETE -H "Authorization: Bearer $AUDIOLLA_TOKEN" $AUDIOLLA_URL/api/ps/htdemucs
# Evict everything
curl -X POST -H "Authorization: Bearer $AUDIOLLA_TOKEN" $AUDIOLLA_URL/unload
POST /v1/audio/separate — returns audio bytes if exactly one stem is requested, otherwise a ZIP.
# Single stem → audio bytes
curl -X POST -H "Authorization: Bearer $AUDIOLLA_TOKEN" \
$AUDIOLLA_URL/v1/audio/separate \
-F "file=@track.wav" \
-F "engine=htdemucs" \
-F "stems=vocals" \
-o vocals.wav
# Multiple stems → ZIP
curl -X POST -H "Authorization: Bearer $AUDIOLLA_TOKEN" \
$AUDIOLLA_URL/v1/audio/separate \
-F "file=@track.wav" \
-F "engine=htdemucs" \
-F "stems=vocals" \
-F "stems=drums" \
-o vocals_drums.zip
# Omit stems= entirely → all stems for that engine
curl -X POST -H "Authorization: Bearer $AUDIOLLA_TOKEN" \
$AUDIOLLA_URL/v1/audio/separate \
-F "file=@track.wav" \
-F "engine=htdemucs" \
-o all_stems.zip
# MP3 output
curl -X POST -H "Authorization: Bearer $AUDIOLLA_TOKEN" \
$AUDIOLLA_URL/v1/audio/separate \
-F "file=@track.wav" \
-F "engine=htdemucs" \
-F "stems=vocals" \
-F "output_format=mp3" \
-o vocals.mp3
Required: file, engine. Optional: stems (repeated form field; default = all stems for that engine), output_format (default wav).
Loading a separation engine evicts other loaded engines first — Demucs is memory-hungry and the operator-default setup runs one engine in memory at a time.
POST /v1/audio/master — mode=reference uses matchering against a reference track; mode=chain runs a pedalboard preset.
# Reference-based mastering
curl -X POST -H "Authorization: Bearer $AUDIOLLA_TOKEN" \
$AUDIOLLA_URL/v1/audio/master \
-F "file=@track.wav" \
-F "mode=reference" \
-F "reference=@ref.wav" \
-o mastered.wav
# Pedalboard chain — preset is REQUIRED (transparent or loud)
curl -X POST -H "Authorization: Bearer $AUDIOLLA_TOKEN" \
$AUDIOLLA_URL/v1/audio/master \
-F "file=@track.wav" \
-F "mode=chain" \
-F "preset=loud" \
-o mastered.wav
# Pedalboard chain with explicit loudness target
curl -X POST -H "Authorization: Bearer $AUDIOLLA_TOKEN" \
$AUDIOLLA_URL/v1/audio/master \
-F "file=@track.wav" \
-F "mode=chain" \
-F "preset=transparent" \
-F "target_lufs=-14" \
-o mastered.wav
Required: file, mode. mode=reference requires reference. mode=chain requires preset (transparent or loud). Optional: target_lufs (range [-70.0, -0.1]), output_format.
Streaming-target LUFS reference values: Spotify -14, Apple Music -16, YouTube -14, broadcast EBU R128 -23.
POST /v1/audio/analyze — returns JSON.
# Specific features
curl -X POST -H "Authorization: Bearer $AUDIOLLA_TOKEN" \
$AUDIOLLA_URL/v1/audio/analyze \
-F "file=@track.wav" \
-F "features=bpm" \
-F "features=key" \
-F "features=loudness"
# Omit features= → returns all of them
curl -X POST -H "Authorization: Bearer $AUDIOLLA_TOKEN" \
$AUDIOLLA_URL/v1/audio/analyze \
-F "file=@track.wav"
Valid features values: bpm, key, loudness, duration, spectral_centroid, rms, zcr.
Common mistake: the feature for integrated LUFS is
loudness, NOTlufs. Asking forfeatures=lufsreturns 400.
POST /v1/audio/transform — applies an array of SoX operations in order.
# Pitch shift up 2 semitones, then add reverb
curl -X POST -H "Authorization: Bearer $AUDIOLLA_TOKEN" \
$AUDIOLLA_URL/v1/audio/transform \
-F "file=@track.wav" \
-F 'operations=[
{"op":"pitch","params":{"n_semitones":2}},
{"op":"reverb","params":{"reverberance":50,"room_scale":80}}
]' \
-F "output_format=wav" \
-o out.wav
# Trim first 30s, pad 2s silence at end, gain -3dB
curl -X POST -H "Authorization: Bearer $AUDIOLLA_TOKEN" \
$AUDIOLLA_URL/v1/audio/transform \
-F "file=@track.wav" \
-F 'operations=[
{"op":"trim","params":{"start_time":0,"end_time":30}},
{"op":"pad","params":{"end_duration":2}},
{"op":"gain","params":{"db":-3}}
]' \
-o trimmed.wav
operations is a JSON array of {"op": "<name>", "params": {...}}. Order matters — ops apply left-to-right.
Ops and their params:
| op | required params | optional params | what it does |
|---|---|---|---|
gain | db (float) | gain in dB | |
equalizer | frequency, gain_db | width_q (default 1.0) | peaking EQ |
compand | attack_time, decay_time, soft_knee_db, tf_points ([[in_db, out_db], ...]) | dynamic range compression | |
reverb | reverberance (0-100, default 50), pre_delay_ms (default 0), room_scale (default 100) | reverb | |
pitch | n_semitones (float) | pitch shift in semitones, not cents | |
tempo | factor (float) | tempo factor (1.5 = 1.5x faster, 0.5 = half speed) | |
rate | samplerate (int) | resample | |
channels | n_channels (int) | mix to N channels | |
trim | start_time (float, sec) | end_time (float, sec; null = end of file) | trim |
pad | start_duration, end_duration (both floats, sec) | pad silence |
Unknown ops return 400 with the valid list.
POST /v1/audio/loudness — without target_lufs, measures integrated LUFS and returns JSON. With target_lufs, normalizes and returns audio bytes.
# Measure
curl -X POST -H "Authorization: Bearer $AUDIOLLA_TOKEN" \
$AUDIOLLA_URL/v1/audio/loudness \
-F "file=@track.wav"
# {"loudness_lufs": -16.3, "target_lufs": null, "normalized": false}
# Normalize to -14 LUFS (streaming target). Response is audio bytes.
# Original measurement is returned in X-Loudness-LUFS response header.
curl -X POST -H "Authorization: Bearer $AUDIOLLA_TOKEN" \
$AUDIOLLA_URL/v1/audio/loudness \
-F "file=@track.wav" \
-F "target_lufs=-14" \
-o normalized.wav
target_lufs must be in [-70.0, -0.1] — outside that range returns 400 (anything closer to 0 will clip catastrophically; anything below -70 silences the audio).
A simple server-side file store under /v1/files. Plain CRUD — upload, list, download, delete. Once a file is staged, every audio endpoint can reference it by relative path via the file_path form field (and the master endpoint accepts reference_path for the reference track).
# Upload (path can have subdirectories: bands/myband/track.wav)
curl -X PUT -H "Authorization: Bearer $AUDIOLLA_TOKEN" \
$AUDIOLLA_URL/v1/files/mytrack.wav \
--data-binary @track.wav
# Use the staged path on any audio call
curl -X POST -H "Authorization: Bearer $AUDIOLLA_TOKEN" \
$AUDIOLLA_URL/v1/audio/separate \
-F "file_path=mytrack.wav" \
-F "engine=htdemucs" \
-F "stems=vocals" \
-o vocals.wav
# Process AND write the result back to staging in one call
curl -X POST -H "Authorization: Bearer $AUDIOLLA_TOKEN" \
$AUDIOLLA_URL/v1/audio/separate \
-F "file_path=mytrack.wav" \
-F "engine=htdemucs" \
-F "stems=vocals" \
-F "output_path=stems/mytrack-vocals.wav"
# → {"path":"stems/mytrack-vocals.wav","size":...,"engine":"htdemucs","stem":"vocals","output_format":"wav"}
# List
curl -H "Authorization: Bearer $AUDIOLLA_TOKEN" $AUDIOLLA_URL/v1/files
# Download
curl -H "Authorization: Bearer $AUDIOLLA_TOKEN" \
$AUDIOLLA_URL/v1/files/mytrack.wav -o copy.wav
# Delete
curl -X DELETE -H "Authorization: Bearer $AUDIOLLA_TOKEN" \
$AUDIOLLA_URL/v1/files/mytrack.wav
Path traversal (.., leading /, etc.) is rejected with 400. Symlinks are not followed. Size cap is AUDIOLLA_MAX_UPLOAD_BYTES.
Every audio endpoint accepts exactly one of three input forms — supplying zero or more than one returns 400:
file — multipart upload (raw bytes in the request)file_path — relative path under the staging area (must exist, populated via PUT /v1/files)file_url — remote URL the server fetches (subject to the AUDIOLLA_FETCH_MODE policy — see below)Audio-producing endpoints (separate, master, transform, loudness with target) also accept one of:
output_path — server writes the result to FILES_DIR / <path>; response is JSON {path, size, ...}output_url — server PUTs the result to a presigned URL; response is JSON {url, size, ...}output_path and output_url are mutually exclusive; both being set is 400.
The master endpoint additionally accepts reference / reference_path / reference_url for the reference track in mode=reference — same exactly-one-of rule.
The server-side URL fetch is disabled by default. To enable it, the operator sets:
AUDIOLLA_FETCH_MODE = disabled | allowlist | denylist (default: disabled)
AUDIOLLA_FETCH_HOSTS = comma-separated host patterns (required when mode=allowlist)
AUDIOLLA_FETCH_SCHEMES = https,http (default: https only)
AUDIOLLA_FETCH_TIMEOUT = 30s (per fetch/upload)
AUDIOLLA_FETCH_ALLOW_PRIVATE = false (allow private/loopback IPs)
AUDIOLLA_FETCH_MAX_REDIRECTS = 5
Host patterns are exact match (bucket.s3.amazonaws.com) or single-wildcard subdomain (*.s3.amazonaws.com, matches any <x>.s3.amazonaws.com but NOT s3.amazonaws.com itself).
Always-on protections regardless of mode:
169.254.169.254) rejected unless AUDIOLLA_FETCH_ALLOW_PRIVATE=trueAUDIOLLA_FETCH_SCHEMES accepted; file://, gopher://, etc. always rejectedLocation re-validated through the full policy before followingAUDIOLLA_MAX_UPLOAD_BYTESIf you're scripting and the server returns URL fetch/upload is disabled (400), tell the user — don't try to bypass it. The operator chose disabled for a reason.
Example — fetch from S3, master, PUT to a presigned URL:
curl -X POST -H "Authorization: Bearer $AUDIOLLA_TOKEN" \
$AUDIOLLA_URL/v1/audio/master \
-F "file_url=https://my-bucket.s3.amazonaws.com/track.wav" \
-F "mode=chain" \
-F "preset=loud" \
-F "output_url=https://my-bucket.s3.amazonaws.com/mastered.wav?X-Amz-Signature=..."
# → {"url":"...","size":...,"engine":"pedalboard-chain","mode":"chain","output_format":"wav"}
audiolla exposes a Model Context Protocol server at /v1/mcp using the streamable HTTP transport. Same auth as REST — pass Authorization: Bearer $AUDIOLLA_TOKEN.
Each audio tool accepts exactly one of file_path or file_url for input (same AUDIOLLA_FETCH_MODE policy as REST). For output, the audio tools default to base64-encoded bytes; pass output_url to PUT to a presigned URL instead (response then carries url + size instead of audio_base64). The separate tool takes output_urls as a per-stem dict when uploading each stem to its own presigned URL.
| Tool | Inputs | Output |
|---|---|---|
list_engines | — | engine catalog with loaded flag |
separate | engine, stems, file_path or file_url, optional output_urls: {stem: url} | base64 stems OR {uploaded_stems: {stem: {url, size}}} |
master | mode, file_path or file_url, reference_path or reference_url (mode=reference), preset (mode=chain), target_lufs, output_url | base64 audio OR {url, size} |
analyze | file_path or file_url, features | librosa feature dict |
transform | operations, file_path or file_url, output_url | base64 audio OR {url, size} |
loudness | file_path or file_url, target_lufs, output_url | measurement JSON or {audio_base64 or url+size, measured_lufs, target_lufs, normalized} |
list_files | — | {files: [...]} |
put_file | path, content_base64 | {path, size} |
get_file | path | {path, size, content_base64} |
delete_file | path | {deleted} |
Audio over MCP is base64-in / base64-out by default — JSON-RPC can't carry raw bytes. The two escape hatches are: stage the file ahead of time and pass file_path (small upload via put_file or out-of-band via REST PUT), or pass file_url / output_url so the server fetches/PUTs directly to S3-style storage. For large files always prefer one of those.
The MCP endpoint is at $AUDIOLLA_URL/v1/mcp. It is JSON-RPC over streamable HTTP; do not try to describe it in OpenAPI or hit it with raw curl — use an MCP client.
features=lufs is wrong, use features=loudness. (LUFS is an integrated loudness measurement, but the feature name on the wire is loudness.)mode=chain without preset returns 400. Always pass preset=transparent or preset=loud.htdemucs_ft rejected on CPU — the server flag cuda_only makes this return 400 unless the running image is psyb0t/audiolla:latest-cuda with --gpus all.separate evicts whatever else is loaded. Pre-warming multiple Demucs variants doesn't survive across separation calls.AUDIOLLA_ENGINE_TTL seconds of inactivity will be slow (model reload). For benchmarks or back-to-back jobs, keep traffic flowing or set AUDIOLLA_PRELOAD server-side./api/ps as a load-progress indicator — it tells you what's loaded right now, not what's being loaded.output_format form field, NOT the upload's file extension. The server transcodes via ffmpeg.transform pitch op takes semitones, not cents — n_semitones: 0.5 = half a semitone up, not a tiny shift.POST /v1/audio/loudness with target_lufs returns audio, not JSON, in the default output mode. The measurement comes back in the X-Loudness-LUFS response header — use -D headers.txt with curl to capture it. If you set output_path or output_url the response IS JSON and measured_lufs is in the body instead.file_url / output_url are disabled by default. If the server returns URL fetch/upload is disabled (400), the operator hasn't enabled AUDIOLLA_FETCH_MODE — don't try to bypass it.output_path and output_url are mutually exclusive. Supplying both is 400. Supplying neither = default inline-bytes response.file, file_path, file_url are mutually exclusive too. Same exactly-one-of rule; zero or more-than-one is 400.GET /v1/engines once at the start of a session to see what's actually configured — AUDIOLLA_ENABLED_ENGINES can hide things./v1/files once and reference via file_path on every subsequent REST call (or the equivalent MCP tools) — no need to re-upload. Chain output_path into the next call's file_path to keep everything server-side until you actually need bytes.AUDIOLLA_MAX_UPLOAD_BYTES (default 200 MB). If unsure, GET /healthz first to confirm the server is up and ask the user to confirm the cap.htdemucs_ft on CPU especially) can take minutes — set a generous curl --max-time and warn the user.htdemucs vs htdemucs_ft) — there is no "auto" mode for separation.