Open source, self hosted, automated Lyric to Song generation of timing tracks for xLights
  • Python 85%
  • TypeScript 12.6%
  • CSS 1.8%
  • JavaScript 0.5%
  • HTML 0.1%
Find a file
Brian McGonagill e34730ff12
Some checks failed
CI / backend (push) Has been cancelled
CI / frontend-unit (push) Has been cancelled
CI / e2e (push) Has been cancelled
CI / docker (push) Has been cancelled
CI / docs-links (push) Has been cancelled
align: chunked alignment kills cumulative drift on long songs
Split the track into ~45s windows and force-align each independently
(fresh ASR + whisperx per chunk, clock resets, no accumulation), then
stitch with one monotonic pass. Fixes both drifting mechanisms: the
whole-track ASR stamps that became whisperx's hard crop windows, and the
global start>=prev_end clamp cascade that lifted every later line.

- plan_chunks: greedy line->window assignment via anchor midpoints
  (linear-in-word fallback), never splits a lyric line
- align_chunked: crop +margin_ms around each window, chunk-local anchors,
  +offset rebias, weighted quality, dominant-engine report
- WhisperXEngine caches the align model across chunk calls (whisperx does
  not cache load_align_model internally)
- PipelineSettings.chunk_ms (default 45000) + chunk_margin_ms (2000);
  generate.py falls back to single-pass for short songs / user LRC anchors /
  non-whisperx engines

plan_chunks bug: drifted late tail anchors (beyond song duration) combined
with linear fallback for unanchored following lines produced non-monotonic
centers where lo_b>hi_b, silently dropping the final lyric lines and leaving
them as 1000ms-stepped empty phantoms. Fixed by clamping both centers and
widening the crop so drifted-tail chunks are always emitted. 12 chunked unit
tests; 238 total green, ruff+mypy clean. Verified live: 66/66 lines, final
chorus 'It's Halloween.' genuinely at 198s (was 210-212s phantoms).
2026-09-24 19:35:52 -05:00
.forgejo/workflows CI: move workflows to Forgejo Actions (.forgejo/workflows); docs reflect self-hosted Forgejo repo 2026-09-23 12:56:54 -05:00
backend align: chunked alignment kills cumulative drift on long songs 2026-09-24 19:35:52 -05:00
docker Release hardening: compose worker on by default (plain up works), fixed placeholder URLs that would fail link-check CI, LRC anchor real-model test (±150ms), markdown link verification 2026-09-23 11:13:56 -05:00
docs CI: move workflows to Forgejo Actions (.forgejo/workflows); docs reflect self-hosted Forgejo repo 2026-09-23 12:56:54 -05:00
frontend Alignment accuracy overhaul (measured on a real song): auto-LRC via ASR+NW line matching, hallucination energy-gate, degenerate-phrase even re-spacing + rough-line reporting, lyric lines never dropped 2026-09-23 16:07:27 -05:00
.dockerignore Epic 5: Docker (backend/frontend/compose+GPU profile, cold-start verified), docs (README, architecture, installation, xlights import guide, alignment tuning, api, release checklist), coverage 92%, G2P stress-digit fix, lyrics size guards 2026-09-23 11:00:02 -05:00
.gitignore gitignore Playwright test artifacts 2026-09-23 11:14:06 -05:00
AGENTS.md align: chunked alignment kills cumulative drift on long songs 2026-09-24 19:35:52 -05:00
LICENSE Epic 5: Docker (backend/frontend/compose+GPU profile, cold-start verified), docs (README, architecture, installation, xlights import guide, alignment tuning, api, release checklist), coverage 92%, G2P stress-digit fix, lyrics size guards 2026-09-23 11:00:02 -05:00
README.md Release hardening: compose worker on by default (plain up works), fixed placeholder URLs that would fail link-check CI, LRC anchor real-model test (±150ms), markdown link verification 2026-09-23 11:13:56 -05:00

SingerMaker

Open-source, self-hosted alternative to AutoLyrics: upload a song and its lyrics, get back an xLights .xtiming singing-face track (phrases, words and Preston-Blair mouth-shape phonemes, time-aligned to the actual vocals) that you import straight into your sequence — so your prop's mouth movements hit the words.

song.mp3 + lyrics.txt ──► Demucs (vocals) ──► forced alignment (WhisperX)
   └─► 3-layer timing model ─► snap to 25/50 ms grid ─► out.xtiming / out.pgo

Features

  • Automatic word timing from the supplied lyrics — WhisperX forced alignment (primary, ±150 ms class accuracy), faster-whisper word stamps (fallback) and an energy-anchored heuristic that never fails the job.
  • Exact mouth shapes: ARPAbet → xLights face labels (AI, E, FV, L, MBP, O, U, WQ, etc, rest) via the CMUdict + Papagayo Preston-Blair tables, the same xLights itself uses.
  • LRC support: paste synced lyrics ([mm:ss.xx]) and each line's timestamp becomes an alignment anchor — tighter and faster.
  • Vocal-stem upload to skip separation, per-job engine/grid settings, app-level user pronunciation dictionary for made-up words.
  • .xtiming and .pgo exports — verified against xLights' own import/export code paths.
  • CPU-only by default (a GPU profile is available). Docker Compose deployment, single-user, no accounts, your music never leaves the box.

Quick start (Docker)

git clone <repository-url> && cd singermaker
docker compose -f docker/docker-compose.yml up -d --build
# open http://localhost:8844

Create a project → upload audio + paste lyrics → Generate → Download .xtiming. Then see docs/xlights-import-guide.md for the 60-second xLights side.

How it works

  1. ffmpeg decodes your audio to 44.1 kHz WAV.
  2. Demucs separates vocals (skipped if you upload your own stem, or set None).
  3. A forced-alignment engine times every lyric word against the vocals.
  4. Words are converted to phonemes: user dictionary → CMUdict → extended dictionary → espeak-ng G2P; unknown words are reported so you can add them.
  5. ARPAbet phonemes map to the ten xLights mouth shapes; word and phoneme spans are distributed inside each word like xLights' own breakdown.
  6. Everything snaps to your chosen grid (25/50 ms) and the 3-layer .xtiming XML is written.

Fine-tuning is expected and easy: xLights' timing tracks are fully editable — drag a word or regenerate a word's phonemes there. (See docs/alignment-tuning.md for making the raw alignment better first.)

Development

cd backend && uv sync --extra dev --extra web --extra ml   # CPU torch deps
uv run uvicorn singermaker.api.app:app --reload --port 8000
cd ../frontend && npm install && npm run dev              # http://localhost:5173
Task Command
Backend unit/API tests cd backend && uv run pytest tests/unit
Backend lint / types uv run ruff check . / uv run mypy singermaker
Real-model integration (downloads models once) uv run pytest tests/real -m real
Frontend unit tests / typecheck / build cd frontend && npm test && npm run typecheck && npm run build
Browser end-to-end npm run test:e2e (needs Playwright browsers)
CLI pipeline uv run singermaker generate song.mp3 lyrics.txt -o out.xtiming --engine heuristic

Architecture

Full details in docs/architecture.md and docs/installation.md.

nginx (SPA + /api proxy) ── FastAPI ── Redis ── Celery workers (CPU torch)
                                 │                    │
                            SQLite + files        model-cache volume

Configuration

Variable Default Meaning
SINGERMAKER_DATA_DIR data/ SQLite DB, uploads, outputs
SINGERMAKER_QUEUE eager eager (threads, dev) or celery (Redis)
SINGERMAKER_BROKER_URL redis://redis:6379/0 Celery broker
HF_HOME / TORCH_HOME container: /cache/... model download cache
OMP_NUM_THREADS host default torch CPU thread cap per job

License

GPL-3.0-or-later. Bundled data: CMUdict (BSD-style, Carnegie Mellon), ARPAbet→mouth mapping derived from Papagayo's phoneme_mapping (GPL, Preston Blair shapes); xLights is a trademark of its authors — this project is unaffiliated, just compatible. See data/README.md for provenance.