All projects

Empty Orchestra

2026

A karaoke maker that strips vocals from any song — or an entire YouTube playlist — and hands back a studio-quality instrumental.

ExpressTypeScriptReactViteTailwind v4yt-dlpDemucsffmpegDocker
Empty Orchestra

The problem

Karaoke means "empty orchestra" in Japanese — and that emptiness is exactly what's hard to find. Instrumental versions exist for chart hits, but good luck finding one for a Nepali classic or an album deep cut. I wanted a tool where any song goes in and a clean instrumental comes out, whether the source is a local file or a YouTube link.

The bar was: upload a song or paste a YouTube URL (playlists included), wait a few minutes, download both the instrumental and the original in high-quality formats.

Architecture

The app is a pnpm workspace with two packages: an Express + TypeScript API and a Vite + React client styled with Tailwind v4 and shadcn components on Base UI.

The interesting part is the processing pipeline, which runs entirely as child processes: yt-dlp fetches audio from YouTube, Demucs (Meta's htdemucs model, running in a project-local Python venv with two-stems mode) separates vocals from everything else, and ffmpeg encodes the result to AAC m4a or 320k MP3.

Jobs flow through an in-memory FIFO queue with concurrency 1 — vocal separation is memory-hungry, and running two Demucs jobs at once would grind a laptop to a halt. Job state persists to a JSON file so a restart doesn't lose history, and the frontend polls every 1.5 seconds for progress.

Decisions and trade-offs

Child processes over Python bindings: keeping yt-dlp, Demucs, and ffmpeg as separate executables means each tool can be upgraded independently, crashes stay isolated from the API server, and progress can be parsed off stderr in real time.

Two-stems mode over full four-stem separation: karaoke only needs vocals-vs-everything, and two-stems roughly halves processing time.

Polling over WebSockets: with concurrency 1 and jobs measured in minutes, a 1.5-second poll is indistinguishable from push updates — and removes a whole class of connection-management code.

The signature UI detail: job progress renders as a karaoke lyric line that fills with yellow as the pipeline advances, using background-clip: text. The progress bar is the product.

Gotchas worth knowing

yt-dlp goes stale fast — YouTube changes its internals constantly, and most "YouTube import is broken" reports trace back to an outdated yt-dlp binary rather than a bug in the app.

Demucs model downloads happen on first run, so the Docker image (node:20-slim + a Python venv + ffmpeg) pre-warms the model to keep first-job latency sane in deployment.

Outcome

Open source under MIT, verified end-to-end with both upload and YouTube import producing valid stereo instrumentals. Local runs never need Docker — clone, install, and sing.