{"slug": "stem-separation-explained-how-music-source-separation-models-split-a-song-with-a", "title": "Stem Separation Explained: How Music Source Separation Models Split a Song, With a Python Example", "summary": "A technical explainer by Tania Alwan details how music source separation models split a finished stereo mix into vocals, drums, bass and other stems, framing it as an underdetermined inverse problem that deep learning has made practical. The piece walks through time-frequency masking with librosa, notes phase-reuse artifacts, and surveys waveform and hybrid architectures including Meta's Hybrid Transformer Demucs, measured by SDR on the MUSDB18 benchmark. It includes a pip-installable Demucs example, a residual-RMS check for comparing models, and a comparison of hosted services such as LALAL.AI, Moises and Voicestars.", "body_md": "*By Tania Alwan*\n\nStem separation, also called music source separation, takes a finished stereo mix and estimates the individual parts that went into it: vocals, drums, bass and everything else. It powers karaoke apps, remix tools and AI cover workflows. It's also a nice example of an \"impossible\" inverse problem that deep learning made practical.\n\nThis post covers how separation works, how quality is measured, and how to run an open-source model yourself in a few lines of Python.\n\nA stereo mix is, roughly, a sum of sources:\n\n```\nmix(t) = vocals(t) + drums(t) + bass(t) + other(t)\n```\n\nGiven only `mix`, we want to estimate each term. Mathematically that's underdetermined: infinitely many combinations sum to the same signal. Models solve it by learning what each kind of source *usually* sounds like from large sets of multitrack recordings.\n\nThe classic deep-learning approach works in the time-frequency domain.\n\n``` python\nimport numpy as np\nimport librosa\n\ny, sr = librosa.load(\"mix.wav\", sr=44100, mono=True)\nS = librosa.stft(y, n_fft=4096, hop_length=1024)\nmag, phase = np.abs(S), np.angle(S)\n\n# a trained model would predict this; here it's a placeholder\nvocal_mask = model.predict(mag)          # shape == mag.shape, values in [0, 1]\n\nvocals = librosa.istft(vocal_mask * mag * np.exp(1j * phase), hop_length=1024)\n```\n\n**Weakness:** reusing the mixture phase limits quality. When two sources overlap in the same bin, a single mask can't fully separate them, which is where \"watery\" artefacts come from.\n\nWaveform models like the original Demucs operate directly on raw audio with a convolutional encoder-decoder, sidestepping the phase problem. Hybrid models combine a spectrogram branch and a waveform branch. Hybrid Transformer Demucs (HT Demucs), released by Meta's research team, adds transformer layers between the two branches to model longer-range context.\n\nOther strong families include band-split architectures, which process different frequency bands with dedicated sub-networks. The open research community iterates on these quickly, often through public challenges.\n\nThe standard metric is **SDR (signal-to-distortion ratio)**, in decibels; higher is better. Benchmarks typically use the MUSDB18 dataset, which contains multitrack songs split into vocals, drums, bass and other.\n\nKeep two caveats in mind:\n\nAlways listen, not just read numbers.\n\nDemucs is open source and installable with pip.\n\n```\npython -m pip install -U demucs\ndemucs song.mp3                       # default model, 4 stems\ndemucs --two-stems=vocals song.mp3    # vocals + no_vocals only\n```\n\nOutputs are written to a `separated/` folder, organised by model name and track. Check `demucs --help` for the model options available in your installed version.\n\n**Practical notes:**\n\n``` python\nimport subprocess, pathlib\n\nfor f in pathlib.Path(\"tracks\").glob(\"*.wav\"):\n    subprocess.run([\"demucs\", \"--two-stems=vocals\", str(f)], check=True)\n```\n\nIf you're comparing models for a project, a simple, repeatable test beats impressions.\n\n``` python\nimport soundfile as sf, numpy as np\nmix, _ = sf.read(\"song.wav\")\nstems = [sf.read(f\"separated/htdemucs/song/{s}.wav\")[0] for s in (\"vocals\",\"drums\",\"bass\",\"other\")]\nresidual = mix - np.sum(stems, axis=0)\nprint(\"residual RMS:\", np.sqrt(np.mean(residual**2)))\n```\n\nand why they happen\n\n**Run it yourself when:**\n\n**Use a hosted service when:**\n\nOn the hosted side there are options like LALAL.AI, Moises and the [Voicestars stems splitter](https://www.voicestars.co/ai-stems-splitter), which outputs six stems (vocals, drums, bass, guitars, piano, other). Pricing models differ (credits, subscriptions, per-minute rates), so compare against your expected volume.\n\nTake a song you own, run `demucs --two-stems=vocals` on it, and listen to the outputs on headphones. Then compare against a hosted tool to see which artefacts each one leaves. If you want a browser comparison point, the [Voicestars stems splitter](https://www.voicestars.co/ai-stems-splitter) is one option.\n\n*Disclosure: this article was written in partnership with Voicestars.*", "url": "https://wpnews.pro/news/stem-separation-explained-how-music-source-separation-models-split-a-song-with-a", "canonical_source": "https://dev.to/taniaalwan/stem-separation-explained-how-music-source-separation-models-split-a-song-with-a-python-example-1k83", "published_at": "2026-11-04 18:46:51+00:00", "updated_at": "2026-11-04 18:54:05.015174+00:00", "lang": "en", "topics": ["machine-learning", "ai-tools", "ai-research"], "entities": ["Tania Alwan", "Meta", "Demucs", "Hybrid Transformer Demucs", "MUSDB18", "librosa", "LALAL.AI", "Moises"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/stem-separation-explained-how-music-source-separation-models-split-a-song-with-a", "markdown": "https://wpnews.pro/news/stem-separation-explained-how-music-source-separation-models-split-a-song-with-a.md", "text": "https://wpnews.pro/news/stem-separation-explained-how-music-source-separation-models-split-a-song-with-a.txt", "jsonld": "https://wpnews.pro/news/stem-separation-explained-how-music-source-separation-models-split-a-song-with-a.jsonld"}}