From 80307a86d069b918a9218d6813d74dab7c255606 Mon Sep 17 00:00:00 2001
From: Thales <>
Date: Mon, 5 Oct 2026 09:55:50 +0100
Subject: [PATCH 1/6] docs(readme): a README that matches 0.20, with the
developer material in docs/
The README's features and comparison described StemDeck as it was in May,
and parts were wrong: no pitch shift, lyrics or click track, one job at a
time, nothing on a phone, extract the Windows zip anywhere.
- Download comes first: the same one-file-per-setup table as the release
notes, Linux and Docker/Unraid included, and install notes per platform
that match the release (the quarantine flag on macOS, a writable folder
for the Windows portable build).
- What you can do, grouped as a musician uses it: split, practise, know
the song, keep and share, Guide, and the phone, with new screenshots
from the current UI. Every line was checked against the code.
- The Honest Comparison is corrected and keeps its honest tone.
- Build from source, configuration, the HTTP API, troubleshooting and the
disk layout move to docs/, word for word, except the disk layout, which
described the job list as in-memory and swept after 24 hours.
522 lines become 310. No em dashes.
Closes #767
---
README.md | 544 +++++++++++------------------------
docs/api.md | 19 ++
docs/build-from-source.md | 141 +++++++++
docs/configuration.md | 94 ++++++
docs/troubleshooting.md | 40 +++
imgs/screenshot/guide.png | Bin 0 -> 73035 bytes
imgs/screenshot/phone.png | Bin 0 -> 100221 bytes
imgs/screenshot/practice.png | Bin 0 -> 22718 bytes
imgs/screenshot/stemdeck.png | Bin 1913608 -> 141396 bytes
9 files changed, 460 insertions(+), 378 deletions(-)
create mode 100644 docs/api.md
create mode 100644 docs/build-from-source.md
create mode 100644 docs/configuration.md
create mode 100644 docs/troubleshooting.md
create mode 100644 imgs/screenshot/guide.png
create mode 100644 imgs/screenshot/phone.png
create mode 100644 imgs/screenshot/practice.png
diff --git a/README.md b/README.md
index f69f0ccc..2dc06248 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,6 @@
-**Free, local stem separation. No account. No upload. No subscription.**
@@ -28,443 +27,246 @@
-Drop in an MP3, WAV, FLAC, OGG/Opus, MP4, or M4A file, or paste a YouTube URL, and StemDeck splits the audio into up to six stems (vocals, drums, bass, guitar, piano, other). Play them back in a DAW-style multitrack mixer: mute, solo, balance levels, zoom the waveform, loop a region, and export individual stems or a custom mix. Everything runs locally on your own machine.
+
+ Drop in a song. Get every instrument on its own track. Practise with them.
+ Runs on your own computer: no account, no upload, no subscription.
+
-> **What is this?** StemDeck is a stem separation tool, not a downloader. Its main job is processing audio you already own: drag an MP3, WAV, FLAC, OGG, or M4A onto the import bar and go. YouTube support is a convenience for content you have the right to process. StemDeck does not store, cache, or redistribute any downloaded content. Everything happens locally and nothing leaves your machine.
+
-> StemDeck is a free, open alternative to cloud stem-splitters like Moises and LALAL.AI: no account, no quota, no uploads, no subscription. If you want stems for personal study and prefer to keep things local and free, StemDeck has you covered. If you need the polish, a mobile app, or deeper musician tooling, the commercial products are a better fit.
+
+
+
-
+StemDeck splits a song into up to six stems, vocals, drums, bass, guitar, piano and other, and opens them in a studio where you can mute, solo, slow down, loop, change the key and play along with a click. Drop in an audio file or paste a YouTube or SoundCloud link. Everything runs on your machine; nothing is uploaded.
-
-## Star History
-
-
-
-
-
-
-
-
-
-## We Recommend
-
-StemDeck is free and **does not accept any money, sponsorship, or funding** from anyone listed below. I share these makers and artists and communities purely for the joy of pointing you toward wonderful people doing beautiful work. Go meet them ❤️
-
-| Category | Name | What they do | Link |
-|---|---|---|---|
-| Artists & Creators | Adrianna Claro | Soul and Contemporary R&B Singer, Songwriter and Composer | [Spotify](https://open.spotify.com/album/10fZ48iJhIe3lWcXSMUlFG) · [Apple Music](https://music.apple.com/us/album/happy/1592026727?i=1592026728) |
-| Artists & Creators | Analog4Lyfe | Analog gear specialist | [@analog4lyfe](https://www.instagram.com/analog4lyfe) |
-| Artists & Creators | Dead röses | Cork-based punk rock band | [@dead_rosesband](https://www.instagram.com/dead_rosesband) |
-| Artists & Creators | Joao Gaspar | Producer, film scorer, touring/session musician | [@jay_glaspar](https://www.instagram.com/jay_glaspar) |
-| Artists & Creators | Killah Trakz | Industry Secret Villain and Media Influencer | [@killahtrakz](https://www.instagram.com/killahtrakz/) · [Spotify](https://open.spotify.com/artist/6nePYOqT4E7D4C48twPtKk) · [Apple Music](https://music.apple.com/us/artist/killah-trakz/371296633) |
-| Artists & Creators | More Notes Less Talk | Gear-focused creative project with a raw, tape-recorded identity | [@morenoteslesstalk](https://www.youtube.com/@morenoteslesstalk) |
-| Artists & Creators | NIHIL | Modern alternative metal from Argentina. Fueled by emotionally charged vocals, anger and emptiness | [@somosnihil](https://www.instagram.com/somosnihil/) · [Spotify](https://open.spotify.com/artist/1OeKplJxFNM6JHrWeo2SaV) |
-| Instrument Builders & Repair | Dlima Guitars | Custom guitars and basses | [@dlimaguitars](https://www.instagram.com/dlimaguitars) |
-| Instrument Builders & Repair | Kris Luthier | Instrument repair and restoration | [@krisluthier](https://www.instagram.com/krisluthier) |
-| Instrument Builders & Repair | Lisbon Guitar Works | Handmade guitars in Lisbon | [dlimaguitars.com](https://dlimaguitars.com) |
-| Media & Community | Not Another Audio Podcast | Audio, music and tech industry podcast by working professionals | [Libsyn](https://directory.libsyn.com/shows/view/id/20f5a1e3-6fea-4d41-8cdf-451ce6fc6cda) |
-| Media & Community | r/bass | Bass-player community | [r/Bass](https://www.reddit.com/r/Bass) |
-| Media & Community | slashCAM | Camera, video, and post-production media | [@slashcam.de](https://www.instagram.com/slashcam.de) |
-| Music & Karaoke Technology | Beltr | Local, subscription-free karaoke software | [beltr.app](https://beltr.app/) |
-| Music & Karaoke Technology | Seratone | TV-based karaoke system | [seratone.audio](https://seratone.audio/) |
-| Music Gear | Empress Effects | Boutique effects pedals | [empresseffects.com](https://empresseffects.com) |
-| Music Gear | Thomann | Large music-equipment retailer | [@thomann.music](https://www.instagram.com/thomann.music) |
-| Writers & Storytellers | Alexandre Borges | Portuguese writer, screenwriter, and cultural commentator | [Books & author profile](https://www.instagram.com/alexgram_b/) |
-
-
----
-
-## Features
-
-**6-stem separation** via Demucs `htdemucs_6s`, with auto-detection of the best Torch device (CUDA on NVIDIA, MPS on Apple Silicon, CPU fallback).
-
-**YouTube and local file import.** Paste a YouTube URL or drop an MP3, WAV, FLAC, OGG/Opus, MP4, or M4A directly onto the import bar.
-
-**DAW-style waveform editor** with min/max sample rendering across all stems, shared normalization, zoom in/out/Fit, loop drag on the ruler, gold playhead overlay, and stem-aligned lanes.
-
-**Stem subset extraction.** Click stem chips to choose which stems to keep. Clicking from "all selected" snaps to "only this one"; subsequent clicks add or remove.
-
-**"Original" backing track.** When you pick a subset, a 7th lane contains the complement (full song minus selected stems), perfect for A/B reference without doubling.
-
-**Downloadable selected mix.** A single `mix.wav` of just your selected stems, summed via ffmpeg amix.
-
-**Per-stem mixer** with volume fader, mute, solo, and "monitor" (solo-only) per stem. State syncs between the preview mixer and the stems sidebar.
-
-**Live VU meters** per stem. Post-gain RMS via Web Audio analysers with peak hold and slow falloff.
-
-**Song analysis** including BPM (librosa beat tracker), key, scale, and confidence (Albrecht-Shanahan profiles), integrated LUFS (BS.1770), and sample peak in dBFS.
-
-**Cancellable jobs.** Cancel mid-pipeline and the runner terminates the active subprocess immediately, deletes the partial job dir, and returns to ready.
-
-**Library panel** with folder-based track organisation, drag-and-drop, search, and trash.
-
----
-
-## Honest Comparison
-
-StemDeck is not trying to compete with commercial stem-separation products. It covers the core use case well and stops there. This table exists so you can make an informed choice rather than discover the gaps after the fact.
-
-| | StemDeck | Moises / LALAL.AI / similar |
-|---|---|---|
-| **Price** | Free, forever | Freemium; credits or subscription required for regular use |
-| **Hosting** | Runs entirely on your machine | Cloud; audio must be uploaded to their servers |
-| **Account / login** | None | Required |
-| **Internet required** | Only for YouTube download and first model fetch (~170 MB, cached after) | Always; no offline use |
-| **Privacy** | Audio never leaves your machine | Audio is uploaded and processed on third-party servers |
-| **Data retention** | You control it; delete anytime | Governed by their privacy policy and retention period |
-| **Stem model** | Demucs `htdemucs_6s` (open source, Meta AI) | Proprietary models, regularly updated, generally higher quality |
-| **Stem count** | 6 (vocals, drums, bass, guitar, piano, other) | Up to 10 depending on service and plan |
-| **Input formats** | YouTube URL, MP3, WAV, FLAC, OGG/Opus, MP4, M4A | MP3, WAV, FLAC, M4A, and more depending on service |
-| **Processing speed** | Depends on your hardware; fast with a GPU, slow on CPU only | Fast regardless of your hardware (runs on their servers) |
-| **Batch processing** | One job at a time | Yes, on paid plans |
-| **Mobile app** | No | iOS and Android |
-| **Extra features** | No (no pitch shift, chord detection, lyrics, click track, BPM tap) | Yes, varies by product |
-| **Polish** | Functional, hobby-grade UI | Polished, production-grade apps |
-| **Source code** | Open source, forkable, self-hostable | Closed source |
-
-If you need speed, quality, mobile access, or the extra musician tooling, the commercial products are worth the money. If you want stems for personal study, prefer to keep audio private, or just want something that runs locally with no strings attached, StemDeck is enough.
+> **A separation tool, not a downloader.** StemDeck is for processing audio you have the right to use. Link support is a convenience; StemDeck does not store, cache or redistribute downloaded content. See the [Disclaimer](#disclaimer).
---
## Download
-Pre-built installers and zips are attached to each [GitHub Release](https://github.com/stemdeckapp/stemdeck/releases).
-
-**macOS**
-
-| DMG | GPU | Chip |
-|---|---|---|
-| `StemDeck-macOS-arm64.dmg` | Apple Silicon (MPS) | M1 and later |
-| `StemDeck-macOS-x64.dmg` | CPU only | Intel |
+Every release has a [download page](https://github.com/stemdeckapp/stemdeck/releases/latest) with one file per setup. Pick **one**.
-Open the DMG, drag StemDeck to Applications, and launch it. On first launch the setup screen downloads the Python runtime (~500 MB), FFmpeg, and the Demucs model (~170 MB). Subsequent launches skip setup and start in seconds. No Python or system dependencies required.
-
-macOS may show a Gatekeeper prompt on first open — right-click the app and choose Open to bypass it.
-
-**Windows**
-
-| Zip | GPU | Approx. size |
-|---|---|---|
-| `StemDeck-Windows-x64.zip` | CPU only | ~700 MB |
-| `StemDeck-Windows-x64.NVIDIA.zip` | NVIDIA CUDA | ~1.6 GB |
-
-Extract the zip anywhere, run `StemDeck.exe`. FFmpeg, the Demucs model, config, and logs live in a `data/` folder next to `StemDeck.exe`, not in AppData; move or copy the whole extracted folder anywhere and it keeps working. On first launch the app verifies the bundled Python runtime and downloads FFmpeg and the Demucs model (~170 MB) into that folder. Subsequent launches skip this and start in seconds. Everything is self-contained; no Python or system dependencies required. Your job/library data stays in its usual location (`~/Documents/StemDeck` by default) and is relocatable anytime from Settings → StemData location.
-
----
+| Your setup | Download |
+|---|---|
+| **Windows**, NVIDIA GPU | `StemDeck-Windows-x64.NVIDIA.zip` |
+| **Windows**, no NVIDIA GPU | `StemDeck-Windows-x64.zip` |
+| **Linux**, NVIDIA GPU | `StemDeck-Linux-x64.NVIDIA.tar.gz` |
+| **Linux**, no NVIDIA GPU | `StemDeck-Linux-x64.tar.gz` |
+| **macOS**, Apple Silicon (M1 and later) | `StemDeck-macOS-arm64.dmg` |
+| **macOS**, Intel | `StemDeck-macOS-x64.dmg` |
+| **Docker / Unraid** | `ghcr.io/stemdeckapp/stemdeck` (Unraid: search "StemDeck" in Apps) |
-## Technologies
-
-
-
-
-
-
+
+Installing, per platform
-StemDeck is built on **[Python 3.12](https://python.org)** managed via **[uv](https://github.com/astral-sh/uv)**, with a **[FastAPI](https://fastapi.tiangolo.com)** backend serving REST and Server-Sent Events. Stem separation uses **[Demucs](https://github.com/facebookresearch/demucs)** (`htdemucs_6s`), Meta AI's open-source 6-stem neural network. The optional on-demand lead/backing vocal split runs the UVR-MDX-NET Karaoke 2 model via **[audio-separator](https://github.com/nomadkaraoke/python-audio-separator)**, trained as part of the **[Ultimate Vocal Remover](https://github.com/Anjok07/ultimatevocalremovergui)** project by Anjok07. YouTube audio is fetched via **[yt-dlp](https://github.com/yt-dlp/yt-dlp)**; transcoding and mixing use **[FFmpeg](https://ffmpeg.org)**. BPM detection and key analysis run on **[librosa](https://librosa.org)**; loudness measurement uses **[pyloudnorm](https://github.com/csteinmetz1/pyloudnorm)** (ITU-R BS.1770). The macOS and Windows desktop shells are **[Tauri v2](https://tauri.app)** (Rust/WKWebView on macOS, Rust/WebView2 on Windows). The frontend is vanilla JS with the Web Audio API, no framework and no build step; waveforms are rendered on `
---
-### Web Server (macOS / Linux / Windows with Python 3.12+)
+## What you can do
-#### Prerequisites
-
-Python 3.12 or newer, `ffmpeg` on your PATH, and [uv](https://github.com/astral-sh/uv). Around 170 MB of free disk for the Demucs model, which downloads automatically on first run.
-
-Optional, for song identification with an AcoustID key: an FFmpeg built with chromaprint (Debian and Ubuntu's `ffmpeg` is), or Chromaprint's `fpcalc` on your PATH (`brew install chromaprint`, `apt install libchromaprint-tools`, or your distro's `chromaprint` package). `./run.sh setup` installs it when your FFmpeg needs it. Without either, songs are identified by their tags only.
-
-#### macOS / Linux (one-shot)
-
-```sh
-git clone https://github.com/stemdeckapp/stemdeck stemdeck && cd stemdeck
-./run.sh setup # installs ffmpeg + uv, runs uv sync
-./run.sh start
-```
+
+
+
-Open .
+### Split any song
-`setup` uses Homebrew on macOS and `apt-get` on Debian/Ubuntu. For other Linux distros, install `ffmpeg` and [uv](https://github.com/astral-sh/uv) manually, then run `uv sync` followed by `./run.sh start`.
+- **Up to six stems** with Meta's open Demucs model: vocals, drums, bass, guitar, piano and other.
+- **Lead and backing vocals** as separate lanes, on demand.
+- **Files or links:** MP3, WAV, FLAC, OGG, Opus, M4A and MP4, or a YouTube or SoundCloud link.
+- **Pick only what you need** before you extract.
+- **Whole playlists** go into an import queue and run one after another.
-#### Windows (PowerShell)
+
+
-Install prerequisites:
-- [uv](https://docs.astral.sh/uv/getting-started/installation/) — `winget install astral-sh.uv`
-- [ffmpeg](https://ffmpeg.org/download.html) — `winget install Gyan.FFmpeg` (or Chocolatey: `choco install ffmpeg`)
+### Practise with it
-```powershell
-git clone https://github.com/stemdeckapp/stemdeck stemdeck; cd stemdeck
-uv sync
-uv run uvicorn app.main:app --host 127.0.0.1 --port 8000 --timeout-graceful-shutdown 5
-```
+- **Mute or solo** any part, and set each one's volume.
+- **Slow it down** to half speed, or anywhere from 0.50x to 0.99x, without changing the pitch.
+- **Loop the hard part**, by dragging across the waveform.
+- **Change the key**, of the whole song or of one part.
+- **Play with a click** that follows the song, with a count-in.
-Open .
+
+
+
+
+
+
+
-> `run.sh` is macOS/Linux only. On Windows use the PowerShell commands above, or run inside WSL.
+### Know the song
-**NVIDIA GPU (CUDA):** install the CUDA-enabled torch build before starting:
+- **Key, tempo, loudness and structure,** found for you.
+- **Synced lyrics** from LRCLIB, or written down from the vocals when none are found.
+- **Song and artist details** from MusicBrainz, Wikipedia and, optionally, Discogs.
-```powershell
-uv pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu124
-$env:STEMDECK_DEMUCS_DEVICE = "cuda"
-uv run uvicorn app.main:app --host 127.0.0.1 --port 8000 --timeout-graceful-shutdown 5
-```
+
+
----
+### Keep and share
-#### Manual (any platform)
+- **Export** what you hear as one file, every part as a `.zip`, only the looped part, or a video with the original picture.
+- **Drag a stem or a loop** straight into your other music software.
+- **A library** with folders, tags, favourites and a Trash that only deletes when you empty it.
-```sh
-git clone https://github.com/stemdeckapp/stemdeck stemdeck && cd stemdeck
-uv sync
-uv run uvicorn app.main:app --reload --timeout-graceful-shutdown 5
-```
+
+
+
-> `--timeout-graceful-shutdown` bounds how long uvicorn waits for open
-> connections when you stop it. StemDeck keeps a long-lived SSE stream open
-> for the import queue while a browser tab is on the app, so without it
-> Ctrl-C waits for that stream instead of exiting.
+### It explains itself
-#### Docker
+
+
+
+
-```sh
-docker compose -f build/docker-compose.yml up --build
-```
+Click **Guide** in the left rail and point at anything. Every button, lane control and number on the screen says what it does, in plain words. While Guide is on, clicks explain instead of acting, so nothing happens by accident.
-Stems land in `./jobs/` on the host. Demucs weights are cached in a named volume so they don't re-download on rebuild. Note: no GPU passthrough on macOS Docker.
+
+
+
-A prebuilt image is published to GHCR. Tags: `edge` (rolling, rebuilt on every merge to main), `latest` (newest stable release), and `X.Y.Z` (pinned to a release).
+### On your phone too
-```sh
-docker run -d --name stemdeck -p 8000:8000 \
- -v /path/to/jobs:/app/jobs \
- -v /path/to/cache:/cache \
- -e STEMDECK_PERSIST_LIBRARY=1 \
- ghcr.io/stemdeckapp/stemdeck:edge
-```
+
+
+
+
-On a Linux host with an NVIDIA GPU (driver + NVIDIA Container Toolkit installed), add `--runtime=nvidia -e NVIDIA_VISIBLE_DEVICES=all` and StemDeck auto-detects CUDA. The image already bundles CUDA-enabled torch, so no separate CUDA install is needed.
+Turn on **Make StemDeck available on your network** in Settings and scan the QR code. Your phone opens your library and mixer over your own Wi-Fi, with speed and key controls. No app store, no cloud.
-#### Unraid
+**Desktop app for Windows, Linux and macOS, a Docker image and an Unraid template, and 11 languages:** English, Deutsch, Español, Français, Bahasa Indonesia, 日本語, 한국어, Polski, Português (Brasil), Português (Portugal) and 简体中文.
-StemDeck is available in Unraid Community Applications: open **Apps**, search "StemDeck", and install. Map the two volumes to persistent appdata paths:
+
+
+
-- `/app/jobs` -> `/mnt/user/appdata/stemdeck/jobs` (library + stems)
-- `/cache` -> `/mnt/user/appdata/stemdeck/cache` (model weights)
+
+Keyboard shortcuts
-The library is persistent by default (`STEMDECK_PERSIST_LIBRARY=1`), so tracks are never auto-deleted. For GPU acceleration, install the **Nvidia Driver** plugin, then set the container's Extra Parameters to `--runtime=nvidia` (the `NVIDIA_VISIBLE_DEVICES` and `NVIDIA_DRIVER_CAPABILITIES` variables are already in the template). CPU-only works with no extra configuration.
+
-#### `run.sh` control script
+| Key | Does |
+|---|---|
+| `Space` | Play or pause |
+| `[` and `]` | Jump back or forward 5 seconds |
+| `L` | Loop on or off |
+| `I` and `O` | Set the loop start or end at the playhead, while looping |
+| `K` | Click track on or off |
-```sh
-./run.sh setup # one-shot: install ffmpeg + uv, then uv sync
-./run.sh start # boots uvicorn in the background
-./run.sh stop # graceful shutdown
-./run.sh restart # stop + start
-./run.sh status # is it running?
-```
+
---
-## How to Use
+## Honest comparison
-1. On the import bar, click stem chips to choose which stems to extract (defaults to all 6).
-2. Paste a YouTube URL **or** drop an audio file (MP3, WAV, FLAC, OGG, MP4, M4A), then click **Process**.
-3. Wait through `Uploading...` / `Downloading...` → `Analyzing...` → `Separating...` → `Mixing tracks...`.
-4. When done, the studio dashboard appears. If you picked a subset, the first lane is **Original** (full song minus your selection); the rest are your isolated stems.
-5. Mix: **Play/Pause/Stop** controls the master transport. **M** mutes a stem, **S** solos it (additive; multiple solos stay audible), **Monitor** solos only that stem and clears others. The volume fader moves 1:1 with drag; double-click resets to 0 dB; `Shift+wheel` gives coarse adjustment and plain wheel gives fine. The **Reset**, **Mute**, and **Solo** toolbar buttons act on all stems at once.
-6. Drag on the ruler to define a loop region; click `Loop` to enable. Use `+` / `-` / `Fit` or `Ctrl/Cmd+wheel` to zoom.
-7. **Download Mix** in the footer gives you a WAV of your selected stems summed together.
+StemDeck is not trying to beat the commercial stem-separation services. It covers the core well and stops there. This table is here so you can choose knowingly rather than find the gaps later.
-**Keyboard shortcuts:** `Space` play/pause · `[` seek -5s · `]` seek +5s · `L` loop · `I` loop in · `O` loop out
+| | StemDeck | Moises, LALAL.AI and similar |
+|---|---|---|
+| **Price** | Free, for good | Freemium; credits or a subscription for regular use |
+| **Where it runs** | On your own machine | In the cloud; your audio is uploaded |
+| **Account** | None | Required |
+| **Internet** | Only for links and the first model download | Always |
+| **Privacy** | Your audio never leaves your machine | Processed on their servers, under their retention policy |
+| **Separation model** | Demucs `htdemucs_6s`, open source from Meta | Proprietary, updated often, generally higher quality |
+| **Stems** | 6, plus lead and backing vocals | Up to 10, depending on the service and plan |
+| **Speed** | Depends on your hardware: fast on a GPU, slow on a CPU | Fast on any hardware |
+| **Many songs at once** | An import queue and whole playlists, run one after another | Yes, on paid plans |
+| **Phone** | Your phone opens StemDeck over your own Wi-Fi; no app store app | iOS and Android apps |
+| **Practice tools** | Speed, key change, loops, click track with count-in, synced lyrics; no chord detection | Varies by product; often chords and more |
+| **Polish** | A one-person open-source project | Polished, production-grade apps |
+| **Source code** | Open source, forkable, self-hostable | Closed |
+
+If you need the best quality, speed on any machine, or a store-bought phone app, the commercial products are worth the money. If you want stems for practice and study, keep your audio private, and like software with no strings attached, StemDeck is enough.
---
-## Configuration
+## We Recommend
-| Variable | Default | Purpose |
-|---|---|---|
-| `STEMDECK_DEMUCS_DEVICE` | auto | Force Torch device: `cuda`, `mps`, or `cpu`. |
-| `STEMDECK_DEMUCS_MODEL` | `htdemucs_6s` | Demucs model name. |
-| `STEMDECK_JOBS_DIR` | `./jobs` | Where job directories land. |
-| `STEMDECK_DATA_DIR` | (none) | Portable mode root; sets all sub-dirs below to live inside it. |
-| `STEMDECK_CACHE_DIR` | `/cache` | Torch model cache directory. |
-| `STEMDECK_DOWNLOADS_DIR` | `/downloads` | yt-dlp download scratch space. |
-| `STEMDECK_MODELS_DIR` | `/models` | Demucs model weights directory. |
-| `STEMDECK_LOGS_DIR` | `/logs` | Log file output directory. |
-| `STEMDECK_FFMPEG_DIR` | (none) | Directory containing a bundled ffmpeg binary. |
-| `STEMDECK_FFMPEG` | `ffmpeg` | Path to the ffmpeg executable. |
-| `STEMDECK_FFPROBE` | `ffprobe` | Path to the ffprobe executable. |
-| `STEMDECK_FPCALC` | `fpcalc` | Path to Chromaprint's fpcalc, used for song fingerprinting when FFmpeg has no chromaprint muxer. |
-| `STEMDECK_MAX_DURATION_SEC` | `1200` | Reject audio longer than this (seconds). |
-| `STEMDECK_JOB_TTL_SECONDS` | `86400` | How long to keep job dirs on disk. |
-| `STEMDECK_MAX_PENDING_JOBS` | `3` | Max queued jobs before returning 503. |
-| `STEMDECK_TIMEOUT_FFMPEG` | `300` | ffmpeg subprocess timeout (seconds). |
-| `STEMDECK_TIMEOUT_ANALYZE` | `120` | Audio analysis timeout (seconds). |
-| `STEMDECK_TIMEOUT_DEMUCS_STALL` | `1800` | Kill Demucs if no output for this many seconds. |
-| `STEMDECK_SSL_CERT` | (none) | PEM certificate; set with the key below to serve https directly. |
-| `STEMDECK_SSL_KEY` | (none) | PEM private key for the certificate above. |
-| `STEMDECK_HTTPS_PORT` | (none) | Serve https on this port *in addition* to the main listener. Set by the desktop app; see below. |
-
-`run.sh` also reads: `HOST` (default `127.0.0.1`), `PORT` (default `8765`), `RELOAD=1` (enable uvicorn auto-reload for development), `FOREGROUND=1` (run in foreground instead of backgrounding).
-
-### Serving other devices: why https is not optional
-
-Transpose is built on `AudioWorklet`, and browsers grant that only to a
-**secure context**. `https://` and `localhost` qualify. A plain
-`http://192.168.1.20:8000` does not, so a phone reaching StemDeck over plain
-http gets working playback and a key control that cannot do anything. There is
-no fallback worth shipping: driving the same DSP from a `ScriptProcessorNode`
-measured around 5% of the audio missing, because that node type drops buffers
-on its own at every size.
-
-So a server that other devices will use terminates TLS, one of three ways:
-
-1. **A reverse proxy** (SWAG, Nginx Proxy Manager, Traefik, Caddy). The usual
- self-hosted shape, and the best one if you already run it. StemDeck reads
- `X-Forwarded-Proto` and the RFC 7239 `Forwarded` header, so an https browser
- over a plain-http upstream hop is recognised as secure and served normally.
-2. **StemDeck itself**, by pointing `STEMDECK_SSL_CERT` and `STEMDECK_SSL_KEY`
- at a certificate and key. uvicorn serves them directly; no extra package is
- installed for this.
-3. **A private overlay network** such as Tailscale, whose addresses are already
- https.
-
-Reaching a plaintext non-local origin with none of those in place is refused
-with a 403 that explains this, rather than served as an app that is quietly
-half-broken. Loopback is always served, so turning this on can never lock the
-host out of its own server.
-
-### The desktop app runs two listeners
-
-The desktop app does the same thing without being configured, because it has
-two audiences that need opposite things.
-
-- **Plain http on `127.0.0.1`** for its own window. Loopback is already a
- secure context, so nothing is lost, and it is the only scheme that works: a
- self-signed certificate would raise a warning page the app window has no way
- to click through.
-- **https on the LAN**, port 8443 by default, for phones and other computers.
- This is the address Settings shows and the QR code points at.
-
-Both listeners serve the same process, so there is one library, one queue and
-one Demucs worker either way.
-
-The certificate is generated on your own machine the first time you enable
-network access, and lives in `/certs/` beside `jobs/` and
-`settings.json`. Nothing is shipped in the download: a certificate in the
-release would publish its private key to everyone who downloaded it, which is
-worse than plain http because it looks secure. It is regenerated automatically
-when your machine's addresses change or the certificate is close to expiring.
-
-Because it is signed by nobody, **your phone will show a "your connection is
-not private" warning the first time**. Tap Advanced, then Continue. Once per
-device, per computer. Settings says so, in red, next to the toggle.
+StemDeck is free and **does not accept any money, sponsorship, or funding** from anyone listed below. I share these makers and artists and communities purely for the joy of pointing you toward wonderful people doing beautiful work. Go meet them ❤️
+| Category | Name | What they do | Link |
+|---|---|---|---|
+| Artists & Creators | Adrianna Claro | Soul and Contemporary R&B Singer, Songwriter and Composer | [Spotify](https://open.spotify.com/album/10fZ48iJhIe3lWcXSMUlFG) · [Apple Music](https://music.apple.com/us/album/happy/1592026727?i=1592026728) |
+| Artists & Creators | Analog4Lyfe | Analog gear specialist | [@analog4lyfe](https://www.instagram.com/analog4lyfe) |
+| Artists & Creators | Dead röses | Cork-based punk rock band | [@dead_rosesband](https://www.instagram.com/dead_rosesband) |
+| Artists & Creators | Joao Gaspar | Producer, film scorer, touring/session musician | [@jay_glaspar](https://www.instagram.com/jay_glaspar) |
+| Artists & Creators | Killah Trakz | Industry Secret Villain and Media Influencer | [@killahtrakz](https://www.instagram.com/killahtrakz/) · [Spotify](https://open.spotify.com/artist/6nePYOqT4E7D4C48twPtKk) · [Apple Music](https://music.apple.com/us/artist/killah-trakz/371296633) |
+| Artists & Creators | More Notes Less Talk | Gear-focused creative project with a raw, tape-recorded identity | [@morenoteslesstalk](https://www.youtube.com/@morenoteslesstalk) |
+| Artists & Creators | NIHIL | Modern alternative metal from Argentina. Fueled by emotionally charged vocals, anger and emptiness | [@somosnihil](https://www.instagram.com/somosnihil/) · [Spotify](https://open.spotify.com/artist/1OeKplJxFNM6JHrWeo2SaV) |
+| Instrument Builders & Repair | Dlima Guitars | Custom guitars and basses | [@dlimaguitars](https://www.instagram.com/dlimaguitars) |
+| Instrument Builders & Repair | Kris Luthier | Instrument repair and restoration | [@krisluthier](https://www.instagram.com/krisluthier) |
+| Instrument Builders & Repair | Lisbon Guitar Works | Handmade guitars in Lisbon | [dlimaguitars.com](https://dlimaguitars.com) |
+| Media & Community | Not Another Audio Podcast | Audio, music and tech industry podcast by working professionals | [Libsyn](https://directory.libsyn.com/shows/view/id/20f5a1e3-6fea-4d41-8cdf-451ce6fc6cda) |
+| Media & Community | r/bass | Bass-player community | [r/Bass](https://www.reddit.com/r/Bass) |
+| Media & Community | slashCAM | Camera, video, and post-production media | [@slashcam.de](https://www.instagram.com/slashcam.de) |
+| Music & Karaoke Technology | Beltr | Local, subscription-free karaoke software | [beltr.app](https://beltr.app/) |
+| Music & Karaoke Technology | Seratone | TV-based karaoke system | [seratone.audio](https://seratone.audio/) |
+| Music Gear | Empress Effects | Boutique effects pedals | [empresseffects.com](https://empresseffects.com) |
+| Music Gear | Thomann | Large music-equipment retailer | [@thomann.music](https://www.instagram.com/thomann.music) |
+| Writers & Storytellers | Alexandre Borges | Portuguese writer, screenwriter, and cultural commentator | [Books & author profile](https://www.instagram.com/alexgram_b/) |
---
-## API
+## Star History
-| Method | Path | Purpose |
-|---|---|---|
-| GET | `/api/health` | Server health and version info |
-| POST | `/api/jobs` | JSON `{url, stems?}` or multipart `file + stems` → `{job_id}` |
-| GET | `/api/jobs` | List completed (library) jobs |
-| GET | `/api/jobs/{id}` | Job state snapshot |
-| GET | `/api/jobs/{id}/events` | SSE stream of job state |
-| POST | `/api/jobs/{id}/cancel` | Terminate active subprocess and cancel job |
-| PATCH | `/api/jobs/{id}/sections` | Save waveform section markers for a job |
-| GET | `/api/jobs/{id}/stems/{name}.wav` | Stream a single stem WAV file |
-| GET | `/api/jobs/{id}/stems/{name}.mp3` | Transcode and stream a stem as MP3 |
-| GET | `/api/jobs/{id}/video.mp4` | Mux the current mix with the source video (MP4 upload or YouTube) into an MP4 |
-| DELETE | `/api/jobs/{id}` | Remove job dir from disk (terminal jobs only) |
+
+
+
+
+
+
+
---
-## Troubleshooting
-
-**`ffmpeg: command not found`:** install ffmpeg and restart with `./run.sh restart`.
+## For developers
-**`WARNING: [youtube] No supported JavaScript runtime`:** install deno (`brew install deno` on macOS) and restart. Downloads still work without it but may pick suboptimal formats.
-
-**First separation is very slow:** Demucs downloads `htdemucs_6s` weights (~170 MB) on first run; cached afterwards.
-
-**Demucs runs on CPU only:** check the startup log for `device=mps` or `device=cuda`. If you see `cpu`, your torch install may be CPU-only.
-
-**Transpose is greyed out on another machine:** the pitch stage is built on `AudioWorklet`, which browsers only expose on a *secure context*. `https://` and `localhost` count. A plain `http://192.168.x.x` does not, so a client opening StemDeck over the network is never given the API and transpose cannot work there. Changing the speed still works on such a client, but it resamples instead, so the key moves with it.
-
-Three ways to get a secure context, in order of least effort:
-
-- **Tunnel to localhost.** On the client: `ssh -N -L 8000:localhost:8000 user@host`, then open `http://localhost:8000`. The origin is now localhost, so everything works, including transpose.
-- **Tailscale Serve.** `tailscale serve 8000` on the host publishes StemDeck on your tailnet over real HTTPS with a genuine certificate, no warnings and nothing to install on the client beyond Tailscale itself. Note the plain Tailscale IP (`100.x.y.z`) is *not* a secure context; it has to go through `serve`.
-- **Any HTTPS reverse proxy** in front of StemDeck: Caddy, nginx, or a tunnel like Cloudflare Tunnel.
-
-**Page reloaded mid-job:** the job keeps running server-side. Wait for it to finish, then resubmit.
+| | |
+|---|---|
+| **[Build from source](docs/build-from-source.md)** | The macOS app, a web server on any platform, Docker and Unraid |
+| **[Configuration](docs/configuration.md)** | Environment variables, and serving other devices over https |
+| **[HTTP API](docs/api.md)** | The endpoints the web app uses |
+| **[Troubleshooting](docs/troubleshooting.md)** | Common problems, and what lives where on disk |
+| **[Model licences](docs/models.md)** | Where each machine-learning model's licence comes from |
-**`./run.sh: Permission denied`:** run `chmod +x run.sh`.
+
+Built with
----
+
-## Layout on Disk
+StemDeck is built on **[Python 3.12](https://python.org)** managed via **[uv](https://github.com/astral-sh/uv)**, with a **[FastAPI](https://fastapi.tiangolo.com)** backend serving REST and Server-Sent Events. Stem separation uses **[Demucs](https://github.com/facebookresearch/demucs)** (`htdemucs_6s`), Meta AI's open-source 6-stem neural network. The optional on-demand lead/backing vocal split runs the UVR-MDX-NET Karaoke 2 model via **[audio-separator](https://github.com/nomadkaraoke/python-audio-separator)**, trained as part of the **[Ultimate Vocal Remover](https://github.com/Anjok07/ultimatevocalremovergui)** project by Anjok07. YouTube audio is fetched via **[yt-dlp](https://github.com/yt-dlp/yt-dlp)**; transcoding and mixing use **[FFmpeg](https://ffmpeg.org)**. BPM detection and key analysis run on **[librosa](https://librosa.org)**; loudness measurement uses **[pyloudnorm](https://github.com/csteinmetz1/pyloudnorm)** (ITU-R BS.1770). The macOS and Windows desktop shells are **[Tauri v2](https://tauri.app)** (Rust/WKWebView on macOS, Rust/WebView2 on Windows). The frontend is vanilla JS with the Web Audio API, no framework and no build step; waveforms are rendered on `
---
@@ -503,20 +305,6 @@ FFmpeg is a GPL build. StemDeck downloads it and runs it as a separate executabl
---
-## Environment Variables
-
-These are for development and testing. Release builds only recognize the variables marked "release".
-
-| Variable | Platform | Scope | Description |
-|---|---|---|---|
-| `STEMDECK_DATA_DIR` | all | release | Override the user data directory (default: platform-standard location) |
-| `STEMDECK_ROOT` | all | release | Override the app root directory (default: derived from executable path) |
-| `STEMDECK_PYTHON` | all | **debug builds only** | Override the Python executable path |
-| `STEMDECK_FFMPEG_URL` | Windows, macOS | release | Override the FFmpeg download URL |
-| `STEMDECK_FFPROBE_URL` | macOS | release | Override the ffprobe download URL |
-
----
-
## Contributing
Issues, feature suggestions, and pull requests are welcome. See open issues for what's planned.
diff --git a/docs/api.md b/docs/api.md
new file mode 100644
index 00000000..d78010b6
--- /dev/null
+++ b/docs/api.md
@@ -0,0 +1,19 @@
+# HTTP API
+
+[Back to the README](../README.md)
+
+The endpoints the web app itself uses. They are not a stable public interface and can change between releases.
+
+| Method | Path | Purpose |
+|---|---|---|
+| GET | `/api/health` | Server health and version info |
+| POST | `/api/jobs` | JSON `{url, stems?}` or multipart `file + stems` → `{job_id}` |
+| GET | `/api/jobs` | List completed (library) jobs |
+| GET | `/api/jobs/{id}` | Job state snapshot |
+| GET | `/api/jobs/{id}/events` | SSE stream of job state |
+| POST | `/api/jobs/{id}/cancel` | Terminate active subprocess and cancel job |
+| PATCH | `/api/jobs/{id}/sections` | Save waveform section markers for a job |
+| GET | `/api/jobs/{id}/stems/{name}.wav` | Stream a single stem WAV file |
+| GET | `/api/jobs/{id}/stems/{name}.mp3` | Transcode and stream a stem as MP3 |
+| GET | `/api/jobs/{id}/video.mp4` | Mux the current mix with the source video (MP4 upload or YouTube) into an MP4 |
+| DELETE | `/api/jobs/{id}` | Remove job dir from disk (terminal jobs only) |
diff --git a/docs/build-from-source.md b/docs/build-from-source.md
new file mode 100644
index 00000000..b790f008
--- /dev/null
+++ b/docs/build-from-source.md
@@ -0,0 +1,141 @@
+# Build from source
+
+[Back to the README](../README.md)
+
+Every way to run StemDeck from this repository: the macOS app, a web server on any platform, Docker and Unraid.
+
+### macOS Native App
+
+Requires Rust, Node.js, and Python 3.12. Builds a self-contained `.app` that downloads its own runtime on first launch.
+
+```sh
+# First time only: add the cross-compilation targets
+rustup target add aarch64-apple-darwin # Apple Silicon
+rustup target add x86_64-apple-darwin # Intel
+
+# Build Apple Silicon
+ARCH=arm64 scripts/macos/make-runtime-pack.sh
+ARCH=arm64 scripts/macos/make-app.sh
+ARCH=arm64 scripts/macos/make-dmg.sh
+
+# Build Intel (requires Rosetta 2 and an x86_64 Python)
+ARCH=x64 scripts/macos/make-runtime-pack.sh
+ARCH=x64 scripts/macos/make-app.sh
+ARCH=x64 scripts/macos/make-dmg.sh
+```
+
+The `.app` lands at `desktop/src-tauri/target//release/bundle/macos/StemDeck.app`. The DMG lands at `.build/macos-dist/StemDeck-macOS-.dmg`.
+
+To run a fresh build directly without the DMG:
+
+```sh
+open desktop/src-tauri/target/aarch64-apple-darwin/release/bundle/macos/StemDeck.app
+```
+
+If macOS blocks the app with a Gatekeeper prompt, run:
+
+```sh
+xattr -dr com.apple.quarantine desktop/src-tauri/target/aarch64-apple-darwin/release/bundle/macos/StemDeck.app
+```
+
+> **Note:** To test a clean first-launch during development, you can wipe previous app data first: `rm -rf ~/Library/Application\ Support/StemDeck`. Don't do this on a real install.
+
+---
+
+### Web Server (macOS / Linux / Windows with Python 3.12+)
+
+#### Prerequisites
+
+Python 3.12 or newer, `ffmpeg` on your PATH, and [uv](https://github.com/astral-sh/uv). Around 170 MB of free disk for the Demucs model, which downloads automatically on first run.
+
+Optional, for song identification with an AcoustID key: an FFmpeg built with chromaprint (Debian and Ubuntu's `ffmpeg` is), or Chromaprint's `fpcalc` on your PATH (`brew install chromaprint`, `apt install libchromaprint-tools`, or your distro's `chromaprint` package). `./run.sh setup` installs it when your FFmpeg needs it. Without either, songs are identified by their tags only.
+
+#### macOS / Linux (one-shot)
+
+```sh
+git clone https://github.com/stemdeckapp/stemdeck stemdeck && cd stemdeck
+./run.sh setup # installs ffmpeg + uv, runs uv sync
+./run.sh start
+```
+
+Open .
+
+`setup` uses Homebrew on macOS and `apt-get` on Debian/Ubuntu. For other Linux distros, install `ffmpeg` and [uv](https://github.com/astral-sh/uv) manually, then run `uv sync` followed by `./run.sh start`.
+
+#### Windows (PowerShell)
+
+Install prerequisites:
+- [uv](https://docs.astral.sh/uv/getting-started/installation/): `winget install astral-sh.uv`
+- [ffmpeg](https://ffmpeg.org/download.html): `winget install Gyan.FFmpeg` (or Chocolatey: `choco install ffmpeg`)
+
+```powershell
+git clone https://github.com/stemdeckapp/stemdeck stemdeck; cd stemdeck
+uv sync
+uv run uvicorn app.main:app --host 127.0.0.1 --port 8000 --timeout-graceful-shutdown 5
+```
+
+Open .
+
+> `run.sh` is macOS/Linux only. On Windows use the PowerShell commands above, or run inside WSL.
+
+**NVIDIA GPU (CUDA):** install the CUDA-enabled torch build before starting:
+
+```powershell
+uv pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu124
+$env:STEMDECK_DEMUCS_DEVICE = "cuda"
+uv run uvicorn app.main:app --host 127.0.0.1 --port 8000 --timeout-graceful-shutdown 5
+```
+
+---
+
+#### Manual (any platform)
+
+```sh
+git clone https://github.com/stemdeckapp/stemdeck stemdeck && cd stemdeck
+uv sync
+uv run uvicorn app.main:app --reload --timeout-graceful-shutdown 5
+```
+
+> `--timeout-graceful-shutdown` bounds how long uvicorn waits for open
+> connections when you stop it. StemDeck keeps a long-lived SSE stream open
+> for the import queue while a browser tab is on the app, so without it
+> Ctrl-C waits for that stream instead of exiting.
+
+#### Docker
+
+```sh
+docker compose -f build/docker-compose.yml up --build
+```
+
+Stems land in `./jobs/` on the host. Demucs weights are cached in a named volume so they don't re-download on rebuild. Note: no GPU passthrough on macOS Docker.
+
+A prebuilt image is published to GHCR. Tags: `edge` (rolling, rebuilt on every merge to main), `latest` (newest stable release), and `X.Y.Z` (pinned to a release).
+
+```sh
+docker run -d --name stemdeck -p 8000:8000 \
+ -v /path/to/jobs:/app/jobs \
+ -v /path/to/cache:/cache \
+ -e STEMDECK_PERSIST_LIBRARY=1 \
+ ghcr.io/stemdeckapp/stemdeck:edge
+```
+
+On a Linux host with an NVIDIA GPU (driver + NVIDIA Container Toolkit installed), add `--runtime=nvidia -e NVIDIA_VISIBLE_DEVICES=all` and StemDeck auto-detects CUDA. The image already bundles CUDA-enabled torch, so no separate CUDA install is needed.
+
+#### Unraid
+
+StemDeck is available in Unraid Community Applications: open **Apps**, search "StemDeck", and install. Map the two volumes to persistent appdata paths:
+
+- `/app/jobs` -> `/mnt/user/appdata/stemdeck/jobs` (library + stems)
+- `/cache` -> `/mnt/user/appdata/stemdeck/cache` (model weights)
+
+The library is persistent by default (`STEMDECK_PERSIST_LIBRARY=1`), so tracks are never auto-deleted. For GPU acceleration, install the **Nvidia Driver** plugin, then set the container's Extra Parameters to `--runtime=nvidia` (the `NVIDIA_VISIBLE_DEVICES` and `NVIDIA_DRIVER_CAPABILITIES` variables are already in the template). CPU-only works with no extra configuration.
+
+#### `run.sh` control script
+
+```sh
+./run.sh setup # one-shot: install ffmpeg + uv, then uv sync
+./run.sh start # boots uvicorn in the background
+./run.sh stop # graceful shutdown
+./run.sh restart # stop + start
+./run.sh status # is it running?
+```
diff --git a/docs/configuration.md b/docs/configuration.md
new file mode 100644
index 00000000..914b0f86
--- /dev/null
+++ b/docs/configuration.md
@@ -0,0 +1,94 @@
+# Configuration
+
+[Back to the README](../README.md)
+
+| Variable | Default | Purpose |
+|---|---|---|
+| `STEMDECK_DEMUCS_DEVICE` | auto | Force Torch device: `cuda`, `mps`, or `cpu`. |
+| `STEMDECK_DEMUCS_MODEL` | `htdemucs_6s` | Demucs model name. |
+| `STEMDECK_JOBS_DIR` | `./jobs` | Where job directories land. |
+| `STEMDECK_DATA_DIR` | (none) | Portable mode root; sets all sub-dirs below to live inside it. |
+| `STEMDECK_CACHE_DIR` | `/cache` | Torch model cache directory. |
+| `STEMDECK_DOWNLOADS_DIR` | `/downloads` | yt-dlp download scratch space. |
+| `STEMDECK_MODELS_DIR` | `/models` | Demucs model weights directory. |
+| `STEMDECK_LOGS_DIR` | `/logs` | Log file output directory. |
+| `STEMDECK_FFMPEG_DIR` | (none) | Directory containing a bundled ffmpeg binary. |
+| `STEMDECK_FFMPEG` | `ffmpeg` | Path to the ffmpeg executable. |
+| `STEMDECK_FFPROBE` | `ffprobe` | Path to the ffprobe executable. |
+| `STEMDECK_FPCALC` | `fpcalc` | Path to Chromaprint's fpcalc, used for song fingerprinting when FFmpeg has no chromaprint muxer. |
+| `STEMDECK_MAX_DURATION_SEC` | `1200` | Reject audio longer than this (seconds). |
+| `STEMDECK_JOB_TTL_SECONDS` | `86400` | How long to keep job dirs on disk. |
+| `STEMDECK_MAX_PENDING_JOBS` | `3` | Max queued jobs before returning 503. |
+| `STEMDECK_TIMEOUT_FFMPEG` | `300` | ffmpeg subprocess timeout (seconds). |
+| `STEMDECK_TIMEOUT_ANALYZE` | `120` | Audio analysis timeout (seconds). |
+| `STEMDECK_TIMEOUT_DEMUCS_STALL` | `1800` | Kill Demucs if no output for this many seconds. |
+| `STEMDECK_SSL_CERT` | (none) | PEM certificate; set with the key below to serve https directly. |
+| `STEMDECK_SSL_KEY` | (none) | PEM private key for the certificate above. |
+| `STEMDECK_HTTPS_PORT` | (none) | Serve https on this port *in addition* to the main listener. Set by the desktop app; see below. |
+
+`run.sh` also reads: `HOST` (default `127.0.0.1`), `PORT` (default `8765`), `RELOAD=1` (enable uvicorn auto-reload for development), `FOREGROUND=1` (run in foreground instead of backgrounding).
+
+### Serving other devices: why https is not optional
+
+Transpose is built on `AudioWorklet`, and browsers grant that only to a
+**secure context**. `https://` and `localhost` qualify. A plain
+`http://192.168.1.20:8000` does not, so a phone reaching StemDeck over plain
+http gets working playback and a key control that cannot do anything. There is
+no fallback worth shipping: driving the same DSP from a `ScriptProcessorNode`
+measured around 5% of the audio missing, because that node type drops buffers
+on its own at every size.
+
+So a server that other devices will use terminates TLS, one of three ways:
+
+1. **A reverse proxy** (SWAG, Nginx Proxy Manager, Traefik, Caddy). The usual
+ self-hosted shape, and the best one if you already run it. StemDeck reads
+ `X-Forwarded-Proto` and the RFC 7239 `Forwarded` header, so an https browser
+ over a plain-http upstream hop is recognised as secure and served normally.
+2. **StemDeck itself**, by pointing `STEMDECK_SSL_CERT` and `STEMDECK_SSL_KEY`
+ at a certificate and key. uvicorn serves them directly; no extra package is
+ installed for this.
+3. **A private overlay network** such as Tailscale, whose addresses are already
+ https.
+
+Reaching a plaintext non-local origin with none of those in place is refused
+with a 403 that explains this, rather than served as an app that is quietly
+half-broken. Loopback is always served, so turning this on can never lock the
+host out of its own server.
+
+### The desktop app runs two listeners
+
+The desktop app does the same thing without being configured, because it has
+two audiences that need opposite things.
+
+- **Plain http on `127.0.0.1`** for its own window. Loopback is already a
+ secure context, so nothing is lost, and it is the only scheme that works: a
+ self-signed certificate would raise a warning page the app window has no way
+ to click through.
+- **https on the LAN**, port 8443 by default, for phones and other computers.
+ This is the address Settings shows and the QR code points at.
+
+Both listeners serve the same process, so there is one library, one queue and
+one Demucs worker either way.
+
+The certificate is generated on your own machine the first time you enable
+network access, and lives in `/certs/` beside `jobs/` and
+`settings.json`. Nothing is shipped in the download: a certificate in the
+release would publish its private key to everyone who downloaded it, which is
+worse than plain http because it looks secure. It is regenerated automatically
+when your machine's addresses change or the certificate is close to expiring.
+
+Because it is signed by nobody, **your phone will show a "your connection is
+not private" warning the first time**. Tap Advanced, then Continue. Once per
+device, per computer. Settings says so, in red, next to the toggle.
+
+## Environment variables for the desktop app
+
+These are for development and testing. Release builds only recognize the variables marked "release".
+
+| Variable | Platform | Scope | Description |
+|---|---|---|---|
+| `STEMDECK_DATA_DIR` | all | release | Override the user data directory (default: platform-standard location) |
+| `STEMDECK_ROOT` | all | release | Override the app root directory (default: derived from executable path) |
+| `STEMDECK_PYTHON` | all | **debug builds only** | Override the Python executable path |
+| `STEMDECK_FFMPEG_URL` | Windows, macOS | release | Override the FFmpeg download URL |
+| `STEMDECK_FFPROBE_URL` | macOS | release | Override the ffprobe download URL |
diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md
new file mode 100644
index 00000000..5f8af38c
--- /dev/null
+++ b/docs/troubleshooting.md
@@ -0,0 +1,40 @@
+# Troubleshooting
+
+[Back to the README](../README.md)
+
+**`ffmpeg: command not found`:** install ffmpeg and restart with `./run.sh restart`.
+
+**`WARNING: [youtube] No supported JavaScript runtime`:** install deno (`brew install deno` on macOS) and restart. Downloads still work without it but may pick suboptimal formats.
+
+**First separation is very slow:** Demucs downloads `htdemucs_6s` weights (~170 MB) on first run; cached afterwards.
+
+**Demucs runs on CPU only:** check the startup log for `device=mps` or `device=cuda`. If you see `cpu`, your torch install may be CPU-only.
+
+**Transpose is greyed out on another machine:** the pitch stage is built on `AudioWorklet`, which browsers only expose on a *secure context*. `https://` and `localhost` count. A plain `http://192.168.x.x` does not, so a client opening StemDeck over the network is never given the API and transpose cannot work there. Changing the speed still works on such a client, but it resamples instead, so the key moves with it.
+
+Three ways to get a secure context, in order of least effort:
+
+- **Tunnel to localhost.** On the client: `ssh -N -L 8000:localhost:8000 user@host`, then open `http://localhost:8000`. The origin is now localhost, so everything works, including transpose.
+- **Tailscale Serve.** `tailscale serve 8000` on the host publishes StemDeck on your tailnet over real HTTPS with a genuine certificate, no warnings and nothing to install on the client beyond Tailscale itself. Note the plain Tailscale IP (`100.x.y.z`) is *not* a secure context; it has to go through `serve`.
+- **Any HTTPS reverse proxy** in front of StemDeck: Caddy, nginx, or a tunnel like Cloudflare Tunnel.
+
+**Page reloaded mid-job:** the job keeps running server-side. Wait for it to finish, then resubmit.
+
+**`./run.sh: Permission denied`:** run `chmod +x run.sh`.
+
+## Layout on disk
+
+```
+jobs//
+└── stems/
+ ├── vocals.wav # the 6 Demucs stems (always present)
+ ├── drums.wav
+ ├── bass.wav
+ ├── guitar.wav
+ ├── piano.wav
+ ├── other.wav
+ ├── original.wav # sum of un-selected stems (subset only)
+ └── mix.wav # ffmpeg amix of selected stems (subset only)
+```
+
+The library itself is one file, `jobs/registry.json`, so it survives a restart. A song's folder is only removed when you empty the Trash, or by the opt-in **Automatically delete finished tracks** setting, which is off unless you turn it on.
diff --git a/imgs/screenshot/guide.png b/imgs/screenshot/guide.png
new file mode 100644
index 0000000000000000000000000000000000000000..b83bacc6da578138358b24b47ecadee53ac3876a
GIT binary patch
literal 73035
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