Blog

Stem separation in five lines of Python

Split a track into stems with the audial-sdk, then the same call as raw REST with curl, plus auto-detected BPM and key and batch processing.

October 9, 2026 4 min readAudial

Most stem separation code you find online is mostly plumbing: install a model, find weights, fight a CUDA version, write a loader, write a writer. If you want stems inside a script rather than inside a research project, you want none of that. Here is the short version with the Audial Python SDK, followed by the same call as a plain REST request, and then the two things people actually ask for next: tempo and key, and processing a whole folder.

Everything below needs an Audial account with an active subscription. Without one the API answers HTTP 402 with code SUBSCRIPTION_REQUIRED, and the SDK raises SubscriptionRequiredError.

Five lines

pip install audial-sdk
import audial

audial.config.set_api_key("your_api_key")
audial.config.set_user_id("your_user_id")
result = audial.stem_split("song.mp3")

That uploads song.mp3, runs separation on Audial's hosted engine, downloads the stems, and returns their paths. You pass a local path; the SDK handles the upload for you. By default the files land under ./audial_results/.

The return value has the same shape for every SDK function:

{
    "execution": {"exeId": "execution-id", "state": "completed"},
    "files": {
        "folder": "./audial_results/execution-id_stem-split",
        "files": {
            "vocals.mp3": "./audial_results/execution-id_stem-split/vocals.mp3",
            "drums.mp3": "./audial_results/execution-id_stem-split/drums.mp3",
        },
    },
}

So reading a stem path looks like this:

vocals = result["files"]["files"]["vocals.mp3"]

If you would rather not hardcode credentials, set AUDIAL_API_KEY and AUDIAL_USER_ID in the environment or in a .env file in your project directory and drop the two config lines. Then it is three lines.

Choosing stems and changing tempo or key

The full call takes a few more arguments:

result = audial.stem_split(
    "song.mp3",
    stems=["vocals", "drums", "full_song_without_vocals"],
    target_bpm=124,
    target_key="Cmaj",
    algorithm="primaudio",
)

Available stems are vocals, drums, bass, other, and the inverse mixes full_song_without_vocals, full_song_without_drums, full_song_without_bass and full_song_without_other. The inverse mixes are useful when you want a backing track without re-summing three files yourself. algorithm accepts primaudio or quintessound.

Leave target_bpm and target_key out and the stems keep the source timing and pitch.

The same thing over raw REST

If Python is not your language, skip the SDK. The base URL is https://api.audialmusic.ai/api, and authentication is two headers: X-API-Key and X-User-ID.

There is an upload endpoint, POST /files/{userId}/upload, which returns a file object you can pass to any function. You usually do not need it. Every function also accepts multipart/form-data, with the file sent inline in the field where the URL would otherwise go:

curl -X POST "https://api.audialmusic.ai/api/functions/run/stem-splitter" \
  -H "X-API-Key: your_api_key" -H "X-User-ID: your_user_id" \
  -F "userId=your_user_id" \
  -F "original=@/path/to/track.mp3" \
  -F 'splitStemsRequest={"stemsRequested":["vocals","drums","bass","other"],"modelName":"primaudio"}'

Two rules make that readable. A file part is stored like an upload and placed in the request body under its own field name, so original becomes the {filename, url, metadata} object the JSON API expects. And a text part that parses as JSON is decoded, which is why splitStemsRequest can be a JSON string rather than a dozen separate fields.

The response is a stems object keyed by stem name, with a URL, filename, BPM and key per stem:

{
  "stems": {
    "vocals": {
      "filename": "vocals.mp3",
      "url": "https://storage.audialmusic.com/files/vocals_67890fghij.mp3",
      "bpm": 120,
      "key": "Cmaj"
    }
  }
}

The JSON-body form of the same call, where you pass original as an object with a url, is on the API reference page.

BPM and key come for free

Notice the bpm and key fields on each stem in that response. Tempo and key are detected automatically for stem splitting, segmentation, sample packs and MIDI, so a file is all you need to send. You do not have to analyze first and then split.

If you want the numbers on their own, before deciding what to do with a track, there is a separate analysis call:

analysis = audial.analyze("song.mp3")
print(analysis["analysis"]["bpm"])
print(analysis["analysis"]["key"])

That also returns characteristics like energy, danceability and loudness. A common pattern is to analyze cheaply across a library, then only split the tracks that pass a filter.

Batch processing a folder

The interesting version of this is not one file, it is two hundred. A straightforward loop, with each track's stems in its own output folder and failures logged rather than fatal:

import os
from pathlib import Path

import audial
from audial.api.exceptions import AudialError, SubscriptionRequiredError

audial.config.set_api_key(os.environ["AUDIAL_API_KEY"])
audial.config.set_user_id(os.environ["AUDIAL_USER_ID"])

SOURCE = Path("~/Music/to_split").expanduser()
DEST = Path("~/Music/stems").expanduser()
AUDIO = {".wav", ".mp3", ".flac", ".aiff", ".m4a", ".ogg", ".aac", ".opus"}

for track in sorted(p for p in SOURCE.iterdir() if p.suffix.lower() in AUDIO):
    out = DEST / track.stem
    if out.exists():
        print(f"skip {track.name}")
        continue
    try:
        result = audial.stem_split(
            str(track),
            stems=["vocals", "drums", "bass", "other"],
            results_folder=str(out),
        )
    except SubscriptionRequiredError:
        raise
    except AudialError as exc:
        print(f"failed {track.name}: {exc}")
        continue
    print(f"{track.name}: {len(result['files']['files'])} stems in {out}")

Three details that matter in a batch run. The results_folder argument per call keeps output tidy and makes the out.exists() check a reliable resume point, so an interrupted run picks up where it stopped. SubscriptionRequiredError is re-raised rather than swallowed, because if that fires once it will fire for every remaining file and you want the run to stop. And other AudialError subclasses, including AudialAuthError and AudialAPIError, are caught per file, so one unreadable track does not end the job.

Uploads are serial here. If you parallelise, keep the pool small and remember that each call is uploading a full audio file, so your own bandwidth is usually the limit before anything else is.

The CLI, for when a script is too much

The SDK ships a command-line interface that covers the same ground, which is often enough for one-off work:

audial config --api-key your_api_key_here
audial config --user-id your_user_id_here
audial config --results-folder ~/Music/stems

audial stem-split song.mp3 --stems vocals,drums,bass,other
audial analyze song.mp3

Where to go next

The same SDK covers mastering, segmentation, sample packs, audio to MIDI, music generation, resynthesis and sung vocals, and every function takes local paths the same way.

Full parameter tables and the JSON form of every request are on the API reference, and you can hear the separation quality before writing any code at the stem splitter.

apipythonstems