StemlineHow it works

How Stemline works

This page is for engineers and curious founders. The landing page is for artists. Same product, two documents.

Stack

Next.js App Router on Vercel Hobby. Supabase Free for Auth, Postgres, and private Storage. All audio DSP runs in the browser: Web Audio for playback and A/B, Web Crypto for AES-GCM, Web Workers for chunking, onnxruntime-web for stem separation. No separate application backend — Route Handlers are a thin metadata and blob gateway.

On-device stem split

A bounce is decoded locally and run through HT-Demucs (ONNX) via onnxruntime-web. Preferred path is WebGPU; WASM is the fallback. Inference is windowed (~10s with 1s overlap) with overlap-add reconstruction. The model caches in IndexedDB so the second split does not re-download weights. Audio never leaves the machine during separation — only after the user saves a version do encrypted chunks hit Storage.

Mobile Safari cannot reliably run this path. Playback and A/B still work on phones; splitting is gated to desktop and framed as a deliberate local-privacy constraint.

Content-addressable versions

Each stem is sliced with FastCDC (fixed-size fallback) into chunks well under Supabase Free's 50 MB object limit. Chunks are addressed by SHA-256 of the sealed blob. A version (commit row) is a manifest of ordered chunk hashes per stem. Unchanged stems carry-forward by hash — a new vocal take does not re-upload drums, bass, or other.

Alts are fork commits: fork_source_commit_id set, parent_commit_id null, stems copied by hash with no re-upload. No merge, no branch table.

Hearing only what changed

Two versions schedule on a shared timeline. Crossfade morphs A↔B with inverse gains. Difference mode aligns stems with a two-stage lag search, then phase-inverts and sums so shared content cancels. Additive-dominant edits play the additions-only residual — the new hats, the reverb tail — not a vague waveform blink.

Encryption and sharing

Each song has a client-generated AES-GCM project key kept in IndexedDB (optional PBKDF2 passphrase backup for the owner). Chunks are sealed before upload; the server stores ciphertext and wrapped keys only. Collaborators get an RSA-OAEP wrap of the project key (account required to contribute).

Listen links are play-only and work without an account: the owner creates a tokenized share row; the URL fragment carries the raw key (#k=…). The fragment is not sent to the server on navigation. Recipients fetch ciphertext manifests by token and decrypt in-browser. If someone strips the fragment, playback cannot unwrap. (Fragment keys can still leak via Referer on outbound navigations — document that risk to collaborators.)

Free-tier constraints

  • Object size ≪ 50 MB → always chunk.
  • ~1 GB Storage → demos stay short; dedup savings are visible in-app.
  • Idle Free projects pause — expect occasional cold starts.

Source, setup, and agent docs live in the GitHub README and docs/.

Start a song