{"id":49400235,"url":"https://github.com/grazzolini/tuneforge","last_synced_at":"2026-04-28T17:35:41.935Z","repository":{"id":352669665,"uuid":"1214721474","full_name":"grazzolini/tuneforge","owner":"grazzolini","description":"Local-first desktop app for musicians learning songs: stem separation, chord/key/tempo detection, pitch shift, retune, export. No cloud, no account.","archived":false,"fork":false,"pushed_at":"2026-04-20T17:05:58.000Z","size":2856,"stargazers_count":1,"open_issues_count":12,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-20T17:40:48.044Z","etag":null,"topics":["audio","chord-detection","demucs","desktop","fastapi","key-detection","local-first","music","musicians","open-source","pitch-shift","play-along","practice","self-hosted","stem-separation","tauri"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/grazzolini.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-04-19T00:39:11.000Z","updated_at":"2026-04-20T17:05:59.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/grazzolini/tuneforge","commit_stats":null,"previous_names":["grazzolini/tuneforge"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/grazzolini/tuneforge","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/grazzolini%2Ftuneforge","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/grazzolini%2Ftuneforge/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/grazzolini%2Ftuneforge/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/grazzolini%2Ftuneforge/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/grazzolini","download_url":"https://codeload.github.com/grazzolini/tuneforge/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/grazzolini%2Ftuneforge/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32392300,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-28T14:34:11.604Z","status":"ssl_error","status_checked_at":"2026-04-28T14:32:37.009Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["audio","chord-detection","demucs","desktop","fastapi","key-detection","local-first","music","musicians","open-source","pitch-shift","play-along","practice","self-hosted","stem-separation","tauri"],"created_at":"2026-04-28T17:35:41.035Z","updated_at":"2026-04-28T17:35:41.920Z","avatar_url":"https://github.com/grazzolini.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Tuneforge\n\nTuneforge is a local-first, open-source desktop app for musicians who want to learn, rehearse, and play along with songs. Drop in any track and Tuneforge will split it into vocals and backing instruments, work out the key, tempo, chord progression, and lyrics, and let you shift the pitch, retune to a reference frequency, follow the transcript during playback, and export a custom version to practice with.\n\nThink \"AI-assisted song toolkit for the player at home\" — but **fully local, single-user, and with no cloud component**. Every track stays on your machine, no account, no upload, no network round-trip after the initial model download.\n\n## Status\n\nPre-1.0. The desktop dev flow (`pnpm dev`) is the fastest way to iterate. Local macOS app/DMG packaging is available with `pnpm package:mac`; generated builds are unsigned, not notarized, and require `ffmpeg`/`ffprobe` on the host `PATH`.\n\n## Features\n\n- Import `mp3`, `wav`, `flac`, `m4a`, `aac`, `ogg`, `mp4`, and `webm`. `mp4` / `webm` are transcoded to a local WAV working file at import time.\n- Key, tempo, and chord-timeline analysis.\n- Local lyrics transcription with segment and word timestamps when available.\n- In-app lyrics editing with transcript refresh and playback follow.\n- Pitch transpose (semitones) and retune (target reference Hz).\n- Stem separation via a local Demucs backend (`htdemucs_ft` by default).\n- Preview rendering (cached) and export to `wav`, `mp3`, or `flac`.\n- Per-project playback session with persistence across navigation.\n\n## Threat Model and Scope\n\nTuneforge is **local-only by design**:\n\n- The backend binds to `127.0.0.1` only.\n- There is **no authentication, no authorization, and no per-user model**.\n- Treat the loopback bind as the only trust boundary. Do not expose the port to a network, do not put it behind a reverse proxy, and do not run it on a shared multi-user host without isolation.\n\nSecurity reports follow the process in [SECURITY.md](./SECURITY.md). \"There is no auth\" is not a vulnerability — it is the design.\n\n## Workspace\n\n- `apps/backend` — FastAPI API, SQLite persistence, job runner, audio analysis/transforms, pytest suite. See [apps/backend/README.md](apps/backend/README.md).\n- `apps/desktop` — Tauri desktop shell and React frontend.\n- `packages/shared-types` — TypeScript contract generated from the backend OpenAPI schema.\n- [MOBILE.md](./MOBILE.md) — Android-first embedded backend architecture and FFmpeg policy.\n\n## Prerequisites\n\n- `pnpm` (version pinned in [package.json](./package.json))\n- [`uv`](https://docs.astral.sh/uv/)\n- Python 3.11\n- `ffmpeg` and `ffprobe` available on `PATH` (install via `brew install ffmpeg`, `apt install ffmpeg`, etc.)\n- Rust toolchain for Tauri\n\n## Setup\n\n```sh\npnpm setup:dev\n```\n\nThat command installs workspace dependencies, syncs the backend Python environment, and regenerates shared API contracts. The first backend sync is heavy because it installs Demucs and Torch. The first stem-separation run will additionally download the Demucs model weights into the local Torch cache.\n\nTo install the optional experimental crema/TensorFlow Advanced Chords backend for local desktop development:\n\n```sh\npnpm setup:dev -- --advanced-chords\n```\n\n`--crema` is accepted as an alias. Advanced Chords remains optional; default setup and mobile paths do not install crema or TensorFlow.\n\n### Linux legacy NVIDIA profile\n\nIf you are on Linux `x86_64` with an older NVIDIA GPU that the default PyTorch build rejects at runtime, use the backend's opt-in legacy NVIDIA sync:\n\n```sh\npnpm setup:dev -- --legacy-nvidia\n```\n\nThat command first performs the normal backend sync, then locally overrides `torch` / `torchaudio` inside `apps/backend/.venv` with the official CUDA 12.6 wheels. The local backend commands (`pnpm dev:backend`, backend test/lint steps inside `pnpm test` / `pnpm lint`) will keep using that override until you reset the backend env:\n\n```sh\npnpm sync:backend:default\n```\n\nTo combine the legacy NVIDIA profile with Advanced Chords:\n\n```sh\npnpm setup:dev -- --legacy-nvidia --advanced-chords\n```\n\nBoth backend sync helpers recreate `apps/backend/.venv` from scratch to avoid stale mixed CUDA stacks when switching profiles. `uv` still reuses its shared cache, so after the first install, switching is usually much faster than a cold download. It is intended for cards like the GTX 1050 Ti. macOS, CI, and the default Linux setup remain unchanged.\n\n## Development\n\nTwo terminals:\n\n```sh\npnpm dev:backend\npnpm dev:desktop\n```\n\nOr both at once:\n\n```sh\npnpm dev\n```\n\nThe backend serves the local API on `http://127.0.0.1:8765/api/v1`.\n\n## Configuration\n\nBackend behavior is environment-driven. Full table is in [apps/backend/README.md](apps/backend/README.md#configuration). The most relevant variables:\n\n| Variable | Default | Purpose |\n| --- | --- | --- |\n| `TUNEFORGE_HOST` | `127.0.0.1` | Bind address. Do not change to a public address. |\n| `TUNEFORGE_PORT` | `8765` | Bind port. |\n| `TUNEFORGE_DATA_DIR` | OS-specific | Override the data directory. |\n| `TUNEFORGE_FFMPEG_PATH` / `TUNEFORGE_FFPROBE_PATH` | `ffmpeg` / `ffprobe` | Override binary lookup. |\n| `TUNEFORGE_STEM_MODEL` | `htdemucs_ft` | Demucs model. |\n| `TUNEFORGE_STEM_DEVICE` | `auto` | `auto` / `cpu` / `mps` / `cuda`. |\n| `TUNEFORGE_LYRICS_MODEL` | `turbo` | Whisper model for lyrics transcription. |\n| `TUNEFORGE_LYRICS_DEVICE` | `auto` | `auto` / `cpu` / `mps` / `cuda`. |\n\nDefault data directory:\n\n- macOS: `~/Library/Application Support/Tuneforge`\n- Linux: `~/.local/share/tuneforge`\n\n## Quality Gates\n\n```sh\npnpm lint\npnpm typecheck\npnpm test\n```\n\nIf you change backend routes or schemas, regenerate the shared contracts and commit the result:\n\n```sh\npnpm contracts:generate\n```\n\nCI fails if `packages/shared-types/src/generated/openapi.ts` drifts from the backend OpenAPI output.\n\n## Packaging\n\nOn macOS, build a local unsigned app bundle and DMG with:\n\n```sh\npnpm package:mac\n```\n\nThe generated artifacts are written under `apps/desktop/src-tauri/target/release/bundle/`:\n\n- `macos/Tuneforge.app`\n- `dmg/Tuneforge_0.1.0_aarch64.dmg` on Apple Silicon\n\nRun packaging from a normal macOS shell so `hdiutil` can create the disk image. Packaged builds require `ffmpeg`/`ffprobe` to be installed on the host system; Tuneforge does not bundle them (see [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md)). The macOS app checks the inherited `PATH` plus common Homebrew and MacPorts install locations when launching the bundled backend.\n\n## CI\n\nGitHub Actions runs two jobs on every push and pull request:\n\n- `backend`: `uv sync`, `ruff`, `mypy`, `pytest`\n- `desktop`: `pnpm install`, `pnpm contracts:generate`, generated-contract drift check, desktop `lint`, `typecheck`, `test`\n\n## Contributing\n\nSee [CONTRIBUTING.md](./CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md). Feature scope is opinionated; please open a feature-request issue before writing significant new code.\n\n## License\n\n[MIT](./LICENSE). Third-party components and their licenses are listed in [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgrazzolini%2Ftuneforge","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgrazzolini%2Ftuneforge","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgrazzolini%2Ftuneforge/lists"}