From 5933cba8e969f2cd1cd66b340642b5405b1fb370 Mon Sep 17 00:00:00 2001 From: James Pine Date: Mon, 16 Mar 2026 06:15:04 -0700 Subject: [PATCH] add release skills and backfill changelog from GitHub releases - Backfill CHANGELOG.md from all 17 GitHub releases (was stale at v0.1.0) - Add draft-release-notes and release-bump agent skills - Remove stale PATCH_NOTES.md, mlx-test/, move PROJECT_STATUS to docs/notes - Minor voicebox-server.spec cleanup --- .agents/skills/draft-release-notes/SKILL.md | 94 +++++ .agents/skills/release-bump/SKILL.md | 124 ++++++ CHANGELOG.md | 435 ++++++++++++++++---- PATCH_NOTES.md | 58 --- backend/voicebox-server.spec | 8 +- docs/{plans => notes}/PROJECT_STATUS.md | 0 mlx-test/.gitignore | 9 - mlx-test/demo.py | 64 --- mlx-test/test_tts.py | 207 ---------- 9 files changed, 591 insertions(+), 408 deletions(-) create mode 100644 .agents/skills/draft-release-notes/SKILL.md create mode 100644 .agents/skills/release-bump/SKILL.md delete mode 100644 PATCH_NOTES.md rename docs/{plans => notes}/PROJECT_STATUS.md (100%) delete mode 100644 mlx-test/.gitignore delete mode 100755 mlx-test/demo.py delete mode 100644 mlx-test/test_tts.py diff --git a/.agents/skills/draft-release-notes/SKILL.md b/.agents/skills/draft-release-notes/SKILL.md new file mode 100644 index 00000000..b844c706 --- /dev/null +++ b/.agents/skills/draft-release-notes/SKILL.md @@ -0,0 +1,94 @@ +--- +name: draft-release-notes +description: Use this skill to draft or update the [Unreleased] section of CHANGELOG.md from the actual changes since the last tag. Run this at any point during development to keep a working copy of the release narrative. Does NOT bump versions or create tags. +--- + +# Draft Release Notes + +## Goal + +Update the `[Unreleased]` section at the top of `CHANGELOG.md` with a narrative release story based on the real changes since the last tag. This is a **non-destructive working copy** — run it as many times as you want during development. + +## Workflow + +1. **Identify the last release tag and gather changes.** + + ```bash + LAST_TAG=$(git tag --list "v*" --sort=-v:refname | head -n 1) + echo "Last tag: $LAST_TAG" + ``` + + Then collect raw material from three sources: + + a. **Commit log since last tag:** + ```bash + git log --oneline "$LAST_TAG"..HEAD + ``` + + b. **GitHub-generated release notes preview** (PR titles, new contributors): + ```bash + gh api repos/:owner/:repo/releases/generate-notes \ + -f tag_name="vNEXT" \ + -f target_commitish="$(git rev-parse HEAD)" \ + -f previous_tag_name="$LAST_TAG" \ + --jq '.body' + ``` + + c. **Diff stat for theme analysis:** + ```bash + git diff --stat "$LAST_TAG"..HEAD + ``` + +2. **Draft the release narrative.** + + Write markdown for the `[Unreleased]` section following the format below. Do not include the `## [Unreleased]` heading itself — just the body content. + +3. **Update CHANGELOG.md.** + + Replace everything between `## [Unreleased]` and the next `## [` heading with the new draft. Preserve the HTML comment header and all existing release sections below. + + The `[Unreleased]` section must always exist and always be the first section after the header comments. + +4. **Do NOT commit, tag, or bump versions.** Just leave the file modified in the working tree. + +## Release Story Format + +Structure the `[Unreleased]` section like this: + +```markdown +## [Unreleased] + + + + + +### +- Bullet points with specifics +- Reference PRs where available: ([#123](https://github.com/jamiepine/voicebox/pull/123)) + +### +- ... + +### Bug Fixes +- ... +``` + +### Style Guidelines + +- **Factual and specific.** Every claim should trace to a real commit or PR. +- **Narrative over list.** Lead with paragraphs that tell the story, then support with bullets. +- **Group by theme, not by commit.** Cluster related changes under descriptive headings. +- **Reference PRs** where they exist, but don't fabricate them. +- **Skip trivial chores** (typo fixes, CI tweaks) unless they're the bulk of the release. +- **Match the voice of existing releases** — look at the v0.2.1 and v0.2.3 entries in CHANGELOG.md for tone reference. + +## When There Are No Changes + +If `git log "$LAST_TAG"..HEAD` is empty, leave the `[Unreleased]` section empty (just the heading) and tell the user there's nothing to draft. + +## Notes + +- This skill only touches the `[Unreleased]` section. It never modifies stamped release sections. +- The agent can be asked to run this skill at any point — mid-feature, before a PR, or right before cutting a release. +- The `release-bump` skill depends on this draft being up to date before it finalizes. diff --git a/.agents/skills/release-bump/SKILL.md b/.agents/skills/release-bump/SKILL.md new file mode 100644 index 00000000..a266494a --- /dev/null +++ b/.agents/skills/release-bump/SKILL.md @@ -0,0 +1,124 @@ +--- +name: release-bump +description: Use this skill to finalize a release. It stamps the [Unreleased] changelog section with a version and date, runs bumpversion to update all version files, and creates the release commit and tag. Only run this when you're ready to ship. +--- + +# Release Bump + +## Goal + +Finalize the changelog draft, bump the version across all tracked files, and create a tagged release commit. After this skill runs, the repo has a clean release commit and tag ready to push. + +## Prerequisites + +- `gh` CLI installed and authenticated (`gh auth status`). +- `bumpversion` installed (`pip install bumpversion` or available in the project venv). +- The `[Unreleased]` section of `CHANGELOG.md` should already contain the release narrative. If it's empty or stale, run the `draft-release-notes` skill first. + +## Workflow + +1. **Verify the working tree is clean** (except `CHANGELOG.md` which may have the draft). + + ```bash + git status --porcelain + ``` + + Only `CHANGELOG.md` (and optionally `.agents/` files) should be modified. If there are other uncommitted changes, stop and ask the user to commit or stash them first. + +2. **Determine the bump level.** + + Ask the user if not specified: `patch`, `minor`, or `major`. Check the current version: + + ```bash + grep '^current_version' .bumpversion.cfg + ``` + +3. **Stamp the changelog.** + + Read the current `[Unreleased]` content from `CHANGELOG.md`. Compute the new version (based on bump level and current version). Then: + + a. Replace the `## [Unreleased]` section body with an empty placeholder. + b. Insert a new stamped section immediately after `## [Unreleased]`: + + ```markdown + ## [Unreleased] + + ## [X.Y.Z] - YYYY-MM-DD + + + ``` + + c. Update the reference links at the bottom of the file: + - Change the `[Unreleased]` link to compare against the new tag + - Add a new link for the new version + + ```markdown + [Unreleased]: https://github.com/jamiepine/voicebox/compare/vX.Y.Z...HEAD + [X.Y.Z]: https://github.com/jamiepine/voicebox/compare/vPREVIOUS...vX.Y.Z + ``` + +4. **Stage the changelog.** + + ```bash + git add CHANGELOG.md + ``` + +5. **Run bumpversion.** + + ```bash + bumpversion --allow-dirty + ``` + + The `--allow-dirty` flag is needed because `CHANGELOG.md` is already staged. bumpversion will: + - Update version strings in all tracked files (see `.bumpversion.cfg`) + - Create a commit with message `Bump version: X.Y.Z -> A.B.C` + - Create a tag `vA.B.C` + + The staged `CHANGELOG.md` will be included in this commit automatically. + +6. **Verify results.** + + ```bash + git show --name-only --stat HEAD + git tag --list "v*" --sort=-v:refname | head -n 5 + ``` + + Confirm the commit contains: + - `CHANGELOG.md` + - `.bumpversion.cfg` + - `tauri/src-tauri/tauri.conf.json` + - `tauri/src-tauri/Cargo.toml` + - `package.json` + - `app/package.json` + - `tauri/package.json` + - `landing/package.json` + - `web/package.json` + - `backend/__init__.py` + + Confirm the new tag exists. + +7. **Do NOT push** unless the user explicitly asks. Report the tag name and suggest: + + ``` + Ready to push. When you're ready: + git push origin main --follow-tags + ``` + +## Version Calculation Reference + +Given current version `X.Y.Z`: +- `patch` -> `X.Y.(Z+1)` +- `minor` -> `X.(Y+1).0` +- `major` -> `(X+1).0.0` + +## Error Recovery + +- If bumpversion fails, the tag won't exist. Fix the issue and re-run — bumpversion is idempotent as long as the tag doesn't already exist. +- If you need to undo a release commit (before pushing): `git tag -d vX.Y.Z && git reset --soft HEAD~1` +- Never amend a release commit that has been pushed. + +## Notes + +- This skill does not create a GitHub Release. That happens when the tag is pushed and CI runs, or the user can create it manually via `gh release create`. +- The release commit message is controlled by `.bumpversion.cfg` (`Bump version: X.Y.Z -> A.B.C`). Do not override it. +- If the user wants to update the GitHub Release body with the changelog narrative after pushing, they can use: `gh release edit vX.Y.Z --notes-file <(sed -n '/## \[X.Y.Z\]/,/## \[/p' CHANGELOG.md | head -n -1)` diff --git a/CHANGELOG.md b/CHANGELOG.md index f3cab820..9056c239 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,96 +1,393 @@ + + + + + # Changelog -All notable changes to Voicebox will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - ## [Unreleased] -### Fixed -- **Profile Name Validation** - Added proper validation to prevent duplicate profile names ([#134](https://github.com/jamiepine/voicebox/issues/134)) - - Users now receive clear error messages when attempting to create or update profiles with duplicate names - - Improved error handling in create and update profile API endpoints - - Added comprehensive test suite for duplicate name validation +## [0.2.3] - 2026-03-15 -## [0.1.0] - 2026-01-25 +The "it works in dev but not in prod" release. This version fixes a series of PyInstaller bundling issues that prevented model downloading, loading, generation, and progress tracking from working in production builds. -### Added +### Model Downloads Now Actually Work -#### Core Features -- **Voice Cloning** - Clone voices from audio samples using Qwen3-TTS (1.7B and 0.6B models) -- **Voice Profile Management** - Create, edit, and organize voice profiles with multiple samples -- **Speech Generation** - Generate high-quality speech from text using cloned voices -- **Generation History** - Track all generations with search and filtering capabilities -- **Audio Transcription** - Automatic transcription powered by Whisper -- **In-App Recording** - Record audio samples directly in the app with waveform visualization +The v0.2.1/v0.2.2 builds could not download or load models that weren't already cached from a dev install. This release fixes the entire chain: -#### Desktop App -- **Tauri Desktop App** - Native desktop application for macOS, Windows, and Linux -- **Local Server Mode** - Embedded Python server runs automatically -- **Remote Server Mode** - Connect to a remote Voicebox server on your network -- **Auto-Updates** - Automatic update notifications and installation +- **Chatterbox, Chatterbox Turbo, and LuxTTS** all download, load, and generate correctly in bundled builds +- **Real-time download progress** — byte-level progress bars now work in production. The root cause: `huggingface_hub` silently disables tqdm progress bars based on logger level, which prevented our progress tracker from receiving byte updates. We now force-enable the internal counter regardless. +- **Fixed Python 3.12.0 `code.replace()` bug** — the macOS build was on Python 3.12.0, which has a [known CPython bug](https://github.com/pyinstaller/pyinstaller/issues/7992) that corrupts bytecode when PyInstaller rewrites code objects. This caused `NameError: name 'obj' is not defined` crashes during scipy/torch imports. Upgraded to Python 3.12.13. -#### API -- **REST API** - Full REST API for voice synthesis and profile management -- **OpenAPI Documentation** - Interactive API docs at `/docs` endpoint -- **Type-Safe Client** - Auto-generated TypeScript client from OpenAPI schema +### PyInstaller Fixes -#### Technical -- **Voice Prompt Caching** - Fast regeneration with cached voice prompts -- **Multi-Sample Support** - Combine multiple audio samples for better voice quality -- **GPU/CPU/MPS Support** - Automatic device detection and optimization -- **Model Management** - Lazy loading and VRAM management -- **SQLite Database** - Local data persistence +- Collect all `inflect` files — `typeguard`'s `@typechecked` decorator calls `inspect.getsource()` at import time, which needs `.py` source files, not just bytecode. Fixes LuxTTS "could not get source code" error. +- Collect all `perth` files — bundles the pretrained watermark model (`hparams.yaml`, `.pth.tar`) needed by Chatterbox at runtime +- Collect all `piper_phonemize` files — bundles `espeak-ng-data/` (phoneme tables, language dicts) needed by LuxTTS for text-to-phoneme conversion +- Set `ESPEAK_DATA_PATH` in frozen builds so the espeak-ng C library finds the bundled data instead of looking at `/usr/share/espeak-ng-data/` +- Collect all `linacodec` files — fixes `inspect.getsource` error in Vocos codec +- Collect all `zipvoice` files — fixes source code lookup in LuxTTS voice cloning +- Copy metadata for `requests`, `transformers`, `huggingface-hub`, `tokenizers`, `safetensors`, `tqdm` — fixes `importlib.metadata` lookups in frozen binary +- Add hidden imports for `chatterbox`, `chatterbox_turbo`, `luxtts`, `zipvoice` backends +- Add `multiprocessing.freeze_support()` to fix resource_tracker subprocess crash in frozen binary +- `--noconsole` now only applied on Windows — macOS/Linux need stdout/stderr for Tauri sidecar log capture +- Hardened `sys.stdout`/`sys.stderr` devnull redirect to test writability, not just `None` check -### Technical Details +### Updater -- Built with Tauri v2 (Rust + React) -- FastAPI backend with async Python -- TypeScript frontend with React Query and Zustand -- Qwen3-TTS for voice cloning -- Whisper for transcription +- Fixed updater artifact generation with `v1Compatible` for `tauri-action` signature files +- Updated `tauri-action` to v0.6 to fix updater JSON and `.sig` generation + +### Other Fixes + +- Full traceback logging on all backend model loading errors (was just `str(e)` before) + +## [0.2.2] - 2026-03-15 + +- Fix Chatterbox model support in bundled builds +- Fix LuxTTS/ZipVoice support in bundled builds +- Auto-update CUDA binary when app version changes +- CUDA download progress bar +- Fix server process staying alive on macOS (SIGHUP handling, watchdog grace period) +- Hide console window when running CUDA binary on Windows + +## [0.2.1] - 2026-03-15 + +Voicebox v0.1.x was a single-engine voice cloning app built around Qwen3-TTS. v0.2.0 is a ground-up rethink: four TTS engines, 23 languages, paralinguistic emotion controls, a post-processing effects pipeline, unlimited generation length, an async generation queue, and support for every major GPU vendor. Plus Docker. + +### New TTS Engines + +#### Multi-Engine Architecture + +Voicebox now runs **four independent TTS engines** behind a thread-safe per-engine backend registry. Switch engines per-generation from a single dropdown — no restart required. + +| Engine | Languages | Size | Key Strengths | +| --------------------------- | --------- | ------- | --------------------------------------------- | +| **Qwen3-TTS 1.7B** | 10 | ~3.5 GB | Highest quality, delivery instructions | +| **Qwen3-TTS 0.6B** | 10 | ~1.2 GB | Lighter, faster variant | +| **LuxTTS** | English | ~300 MB | CPU-friendly, 48 kHz output, 150x realtime | +| **Chatterbox Multilingual** | 23 | ~3.2 GB | Broadest language coverage, zero-shot cloning | +| **Chatterbox Turbo** | English | ~1.5 GB | 350M params, low latency, paralinguistic tags | + +#### Chatterbox Multilingual — 23 Languages ([#257](https://github.com/jamiepine/voicebox/pull/257)) + +Zero-shot voice cloning in Arabic, Chinese, Danish, Dutch, English, Finnish, French, German, Greek, Hebrew, Hindi, Italian, Japanese, Korean, Malay, Norwegian, Polish, Portuguese, Russian, Spanish, Swahili, Swedish, and Turkish. + +#### LuxTTS — Lightweight English TTS ([#254](https://github.com/jamiepine/voicebox/pull/254)) + +A fast, CPU-friendly English engine. ~300 MB download, 48 kHz output, runs at 150x realtime on CPU. + +#### Chatterbox Turbo — Expressive English ([#258](https://github.com/jamiepine/voicebox/pull/258)) + +A fast 350M-parameter English model with inline paralinguistic tags. + +#### Paralinguistic Tags Autocomplete ([#265](https://github.com/jamiepine/voicebox/pull/265)) + +Type `/` in the text input with Chatterbox Turbo selected to open an autocomplete for **9 expressive tags**: `[laugh]` `[chuckle]` `[gasp]` `[cough]` `[sigh]` `[groan]` `[sniff]` `[shush]` `[clear throat]` + +### Generation + +#### Unlimited Generation Length — Auto-Chunking ([#266](https://github.com/jamiepine/voicebox/pull/266)) + +Long text is now automatically split at sentence boundaries, generated per-chunk, and crossfaded back together. Engine-agnostic. + +- Auto-chunking limit slider — 100–5,000 chars (default 800) +- Crossfade slider — 0–200ms (default 50ms) +- Max text length raised to 50,000 characters +- Smart splitting respects abbreviations, CJK punctuation, and `[tags]` + +#### Asynchronous Generation Queue ([#269](https://github.com/jamiepine/voicebox/pull/269)) + +Generation is now fully non-blocking. Serial execution queue prevents GPU contention. Real-time SSE status streaming. + +#### Generation Versions + +Every generation now supports multiple versions with provenance tracking — original, effects versions, takes, source tracking, version pinning in stories, and favorites. + +### Post-Processing Effects ([#271](https://github.com/jamiepine/voicebox/pull/271)) + +A full audio effects system powered by Spotify's `pedalboard` library: Pitch Shift, Reverb, Delay, Chorus/Flanger, Compressor, Gain, High-Pass Filter, Low-Pass Filter. 4 built-in presets, custom presets, per-profile default effects, and live preview. ### Platform Support -- macOS (Apple Silicon and Intel) -- Windows -- Linux (AppImage) +- **Windows Support** ([#272](https://github.com/jamiepine/voicebox/pull/272)) — Full Windows support with CUDA GPU detection +- **Linux** ([#262](https://github.com/jamiepine/voicebox/pull/262)) — AMD ROCm, NVIDIA GBM fix, WebKitGTK mic access (build from source) +- **NVIDIA CUDA Backend Swap** ([#252](https://github.com/jamiepine/voicebox/pull/252)) — Download and swap in CUDA backend from within the app +- **Intel Arc (XPU) and DirectML** — PyTorch backend supports Intel Arc and DirectML +- **Docker + Web Deployment** ([#161](https://github.com/jamiepine/voicebox/pull/161)) — 3-stage build, non-root runtime, health checks +- **Whisper Turbo** — Added `openai/whisper-large-v3-turbo` as a transcription model option ---- +### Model Management ([#268](https://github.com/jamiepine/voicebox/pull/268)) -## [Unreleased] +Per-model unload, custom models directory, model folder migration, download cancel/clear UI ([#238](https://github.com/jamiepine/voicebox/pull/238)), restructured settings UI. -### Fixed -- Audio export failing when Tauri save dialog returns object instead of string path -- OpenAPI client generator script now documents the local backend port and avoids an unused loop variable warning +### Security & Reliability -### Added -- **justfile** - Comprehensive development workflow automation with commands for setup, development, building, testing, and code quality checks - - Cross-platform support (macOS, Linux, Windows) - - Python version detection and compatibility warnings - - Self-documenting help system with `just --list` +- CORS hardening ([#88](https://github.com/jamiepine/voicebox/pull/88)) +- Network access toggle ([#133](https://github.com/jamiepine/voicebox/pull/133)) +- Offline crash fix ([#152](https://github.com/jamiepine/voicebox/pull/152)) +- Atomic audio saves ([#263](https://github.com/jamiepine/voicebox/pull/263)) +- Filesystem health endpoint +- Chatterbox float64 dtype fix ([#264](https://github.com/jamiepine/voicebox/pull/264)) -### Changed -- **README** - Updated Quick Start with justfile-based setup instructions +### Accessibility ([#243](https://github.com/jamiepine/voicebox/pull/243)) -### Removed -- **Makefile** - Replaced by justfile (cross-platform, simpler syntax) +Screen reader support, keyboard navigation, state-aware `aria-label` attributes on all interactive controls. ---- +### UI Polish -## [Unreleased - Planned] +- Redesigned landing page ([#274](https://github.com/jamiepine/voicebox/pull/274)) +- Voices tab overhaul with inline inspector +- Responsive layout improvements +- Duplicate profile name validation ([#175](https://github.com/jamiepine/voicebox/pull/175)) -### Planned -- Real-time streaming synthesis -- Conversation mode with multiple speakers -- Voice effects (pitch shift, reverb, M3GAN-style) -- Timeline-based audio editor -- Additional voice models (XTTS, Bark) -- Voice design from text descriptions -- Project system for saving sessions -- Plugin architecture +### Community Contributors ---- +[@haosenwang1018](https://github.com/haosenwang1018), [@Balneario-de-Cofrentes](https://github.com/Balneario-de-Cofrentes), [@ageofalgo](https://github.com/ageofalgo), [@mikeswann](https://github.com/mikeswann), [@rayl15](https://github.com/rayl15), [@mpecanha](https://github.com/mpecanha), [@ways2read](https://github.com/ways2read), [@ieguiguren](https://github.com/ieguiguren), [@Vaibhavee89](https://github.com/Vaibhavee89), [@pandego](https://github.com/pandego), [@luminest-llc](https://github.com/luminest-llc) +## [0.1.13] - 2026-02-23 + +### Stability and reliability + +- [#95](https://github.com/jamiepine/voicebox/pull/95) Fix: selecting 0.6B model still downloads and uses 1.7B +- [#93](https://github.com/jamiepine/voicebox/pull/93) fix(mlx): bundle native libs and broaden error handling for Apple Silicon +- [#79](https://github.com/jamiepine/voicebox/pull/79) fix: handle non-ASCII filenames in Content-Disposition headers +- [#78](https://github.com/jamiepine/voicebox/pull/78) fix: guard getUserMedia call against undefined mediaDevices in non-secure contexts +- [#77](https://github.com/jamiepine/voicebox/pull/77) fix: await for confirmation before deleting voices and channels +- [#128](https://github.com/jamiepine/voicebox/pull/128) fix: resolve multiple issues (#96, #119, #111, #108, #121, #125, #127) +- [#40](https://github.com/jamiepine/voicebox/pull/40) Fix: audio export path resolution + +### Build and packaging + +- [#122](https://github.com/jamiepine/voicebox/pull/122) fix(web): add @tailwindcss/vite plugin to web config +- [#126](https://github.com/jamiepine/voicebox/pull/126) Create requirements.txt + +### UX and docs + +- [#44](https://github.com/jamiepine/voicebox/pull/44) Enhances floating generate box UX +- [#57](https://github.com/jamiepine/voicebox/pull/57) chore: updates repo URL in README +- [#146](https://github.com/jamiepine/voicebox/pull/146) Add Spacebot banner to landing page +- [#1](https://github.com/jamiepine/voicebox/pull/1) Improvements + +## [0.1.12] - 2026-01-31 + +### Model Download UX Overhaul + +- Real-time download progress tracking with accurate percentage and speed info +- No more downloading notifications during generation even when its not downloading +- Better error handling and status reporting throughout the download process + +### Other Improvements + +- Enhanced health check endpoint with GPU type information +- Improved model caching verification +- More reliable SSE progress updates +- Actual update notifications — no need to manually check in settings anymore + +## [0.1.11] - 2026-01-30 + +- Fixed transcriptions on MLX +- Fixed model download progress (finally) + +## [0.1.10] - 2026-01-30 + +### Faster generation on Apple Silicon + +Massive speed gains, from around 20s per generation to 2-3s. Added native MLX backend support for Apple Silicon, providing significantly faster TTS and STT generation on M-series macOS machines. + +- **MLX Backend** — New backend implementation optimized for Apple Silicon using MLX framework +- **Dynamic Backend Selection** — Automatically detects platform and selects between MLX (macOS) and PyTorch (other platforms) +- Refactored TTS and STT logic into modular backend implementations +- Updated build process to include MLX-specific dependencies for macOS builds + +## [0.1.9] - 2026-01-30 + +### Improved voice profile creation flow + +- Voice create drafts: No longer lose work if you close the modal +- Fixed whisper only transcribing English or Chinese, now has support for all languages + +### Improved Stories editor + +- Added spacebar for play/pause +- Timeline now auto-scrolls to follow playhead during playback +- Fixed misalignment of the items with mouse when picking up +- Fixed hitbox for selecting an item +- Fixed playhead jumping forward when pressing play + +### Generation box improvements + +- Instruct mode no longer wipes prompt text +- Improved UI cleanliness + +### Misc + +- Fixed "Model downloading" toast during generation when model is already downloaded + +## [0.1.8] - 2026-01-29 + +### Model Download Timeout Issues + +Fixed critical issue where model downloads would fail with "Failed to fetch" errors on Windows. Refactored download endpoints to return immediately and continue downloads in background. + +### Cross-Platform Cache Path Issues + +Fixed hardcoded `~/.cache/huggingface/hub` paths that don't work on Windows. All cache paths now use `hf_constants.HF_HUB_CACHE` for proper cross-platform support. + +### Windows Process Management + +- Added `/shutdown` endpoint for graceful server shutdown on Windows +- Added `gpu_type` field to health check response + +## [0.1.7] - 2026-01-29 + +- Trim and split audio clips in Story Editor +- Auto-activation of stories in Story Editor with visible playhead +- Conditional auto-play support in AudioPlayer for better user control +- Refactored audio loading across HistoryTable, SampleList, and generation forms +- Audio now only auto-plays when explicitly intended, preventing unexpected playback + +## [0.1.6] - 2026-01-29 + +### Introducing Stories + +A full voice editor for composing podcasts and generated conversations. + +- **Stories Editor** — Create multi-voice narratives, podcasts, or conversations with a timeline-based editor +- Compose tracks with different voices +- Edit and arrange audio segments inline +- Build generated conversations with multiple participants +- **Improved Voice Generation UI** — Auto-resizing input, default voice selection, better layout +- **Track Editor Integration** — Inline track editing within story items + +## [0.1.5] - 2026-01-28 + +Fixed recording length limit at 0:29 to auto stop instead of passing the limit and getting an error, which would cause users to lose their recording. + +## [0.1.4] - 2026-01-28 + +- Audio channel management system +- Native audio playback handling in AudioPlayer component +- Refactored ConnectionForm and Checkbox components +- Improved layout consistency and responsiveness +- Added safe area constants for better responsive design + +## [0.1.3] - 2026-01-27 + +- Improved the generate textbox +- Maybe fixed Windows autoupdate restarting entire computer + +## [0.1.2] - 2026-01-27 + +### Audio Capture & Format Conversion + +- Added audio format conversion util +- Enhanced system audio capture on macOS and Windows +- Improved audio recording hooks +- Added audio input entitlement for macOS +- Added audio capture tests + +### Update System + +- Enhanced auto-updater functionality and update status display + +## [0.1.1] - 2026-01-27 + +### Platform Support + +- **macOS Audio Capture** — Native audio capture support for sample creation +- **Windows Audio Capture** — WASAPI implementation with improved thread safety +- **Linux Support** — Temporarily removed builds due to runner disk space constraints + +### Audio Features + +- Play/pause for audio samples across all components +- Three new sample components: Recording, System capture, Upload with drag-and-drop +- Audio validation, error handling, and consistent cleanup + +### Voice Profile Management + +- Profile import with file size validation (100MB limit) +- Enhanced profile form with new audio sample components +- Drag-and-drop support for audio file uploads + +### Server Management + +- Changed default URL from `localhost:8000` to `127.0.0.1:17493` +- Server reuse logic, "keep server running" preference, orphaned process handling + +### Build & Release + +- Added `.bumpversion.cfg` for automated version management +- Enhanced icon generation script for multi-size Windows icons + +### Bug Fixes + +- Fixed date formatting for timezone-less date strings +- Fixed getLatestRelease file filtering +- Improved audio duration metadata on Windows + +## [0.1.0] - 2026-01-27 + +The first public release of Voicebox — an open-source voice synthesis studio powered by Qwen3-TTS. + +### Voice Cloning with Qwen3-TTS + +- Automatic model download from HuggingFace +- Multiple model sizes (1.7B and 0.6B) +- Voice prompt caching for instant regeneration +- English and Chinese support + +### Voice Profile Management + +- Create profiles from audio files or record directly in the app +- Multiple samples per profile for higher quality cloning +- Import/Export profiles +- Automatic transcription via Whisper + +### Speech Generation + +- Simple text-to-speech with profile selection +- Seed control for reproducible generations +- Long-form support up to 5,000 characters + +### Generation History + +- Full history with metadata +- Search by text content +- Inline playback and download + +### Flexible Deployment + +- Local mode with bundled backend +- Remote mode for GPU servers on your network +- One-click server setup + +### Desktop Experience + +- Built with Tauri v2 (Rust) — native performance, not Electron +- Cross-platform: macOS and Windows +- No Python installation required + +### Tech Stack + +Tauri v2, React, TypeScript, Tailwind CSS, FastAPI, Qwen3-TTS, Whisper, SQLite + +[Unreleased]: https://github.com/jamiepine/voicebox/compare/v0.2.3...HEAD +[0.2.3]: https://github.com/jamiepine/voicebox/compare/v0.2.2...v0.2.3 +[0.2.2]: https://github.com/jamiepine/voicebox/compare/v0.2.1...v0.2.2 +[0.2.1]: https://github.com/jamiepine/voicebox/compare/v0.1.13...v0.2.1 +[0.1.13]: https://github.com/jamiepine/voicebox/compare/v0.1.12...v0.1.13 +[0.1.12]: https://github.com/jamiepine/voicebox/compare/v0.1.11...v0.1.12 +[0.1.11]: https://github.com/jamiepine/voicebox/compare/v0.1.10...v0.1.11 +[0.1.10]: https://github.com/jamiepine/voicebox/compare/v0.1.9...v0.1.10 +[0.1.9]: https://github.com/jamiepine/voicebox/compare/v0.1.8...v0.1.9 +[0.1.8]: https://github.com/jamiepine/voicebox/compare/v0.1.7...v0.1.8 +[0.1.7]: https://github.com/jamiepine/voicebox/compare/v0.1.6...v0.1.7 +[0.1.6]: https://github.com/jamiepine/voicebox/compare/v0.1.5...v0.1.6 +[0.1.5]: https://github.com/jamiepine/voicebox/compare/v0.1.4...v0.1.5 +[0.1.4]: https://github.com/jamiepine/voicebox/compare/v0.1.3...v0.1.4 +[0.1.3]: https://github.com/jamiepine/voicebox/compare/v0.1.2...v0.1.3 +[0.1.2]: https://github.com/jamiepine/voicebox/compare/v0.1.1...v0.1.2 +[0.1.1]: https://github.com/jamiepine/voicebox/compare/v0.1.0...v0.1.1 [0.1.0]: https://github.com/jamiepine/voicebox/releases/tag/v0.1.0 diff --git a/PATCH_NOTES.md b/PATCH_NOTES.md deleted file mode 100644 index 2e0c983e..00000000 --- a/PATCH_NOTES.md +++ /dev/null @@ -1,58 +0,0 @@ -# Voicebox Offline Mode Fix - -## Problem -Voicebox crashes when generating speech if HuggingFace is unreachable, even when models are fully cached locally. - -**Root Cause:** -- Voicebox downloads `mlx-community/Qwen3-TTS-12Hz-1.7B-Base-bf16` (MLX optimized version) -- But `mlx_audio.tts.load()` tries to fetch `config.json` from original repo `Qwen/Qwen3-TTS-12Hz-1.7B-Base` -- This network request fails → server crashes with `RemoteDisconnected` - -**Related Issues:** -- Issue #150: "Internet connection required, even though models are downloaded?" -- Issue #151: "API Stability Issues: Model Loading Hangs and Server Crashes" - -## Solution -Two-part fix: - -### 1. Monkey-patch huggingface_hub (`backend/utils/hf_offline_patch.py`) -- Intercepts cache lookup functions -- Forces offline mode early (before mlx_audio imports) -- Adds debug logging for cache hits/misses - -### 2. Symlink original repo to MLX version (`ensure_original_qwen_config_cached()`) -- When original `Qwen/Qwen3-TTS-12Hz-1.7B-Base` cache doesn't exist -- But MLX `mlx-community/Qwen3-TTS-12Hz-1.7B-Base-bf16` does exist -- Creates a symlink so cache lookups succeed - -## Files Changed -- `backend/backends/mlx_backend.py` - Added patch imports at top -- `backend/utils/hf_offline_patch.py` - New patch module - -## Testing -To test this fix: -1. Build Voicebox from source: `just build` -2. Disconnect from internet -3. Try generating speech -4. Should work without network requests - -## Build Instructions - -```bash -# Install dependencies -just setup - -# Build the app -just build - -# Or build just the server -just build-server -``` - -## Notes -- The patch is applied automatically when `mlx_backend.py` is imported -- Set `VOICEBOX_OFFLINE_PATCH=0` to disable the patch -- The symlink approach works because the config.json is compatible between versions - ---- -*Patch contributed by community* diff --git a/backend/voicebox-server.spec b/backend/voicebox-server.spec index 6a7dc6d3..e38d7233 100644 --- a/backend/voicebox-server.spec +++ b/backend/voicebox-server.spec @@ -6,7 +6,7 @@ from PyInstaller.utils.hooks import copy_metadata datas = [] binaries = [] -hiddenimports = ['backend', 'backend.main', 'backend.config', 'backend.database', 'backend.models', 'backend.profiles', 'backend.history', 'backend.tts', 'backend.transcribe', 'backend.platform_detect', 'backend.backends', 'backend.backends.pytorch_backend', 'backend.utils.audio', 'backend.utils.cache', 'backend.utils.progress', 'backend.utils.hf_progress', 'backend.utils.validation', 'backend.cuda_download', 'backend.effects', 'backend.utils.effects', 'backend.versions', 'pedalboard', 'chatterbox', 'chatterbox.tts_turbo', 'chatterbox.mtl_tts', 'backend.backends.chatterbox_backend', 'backend.backends.chatterbox_turbo_backend', 'backend.backends.luxtts_backend', 'zipvoice', 'zipvoice.luxvoice', 'torch', 'transformers', 'fastapi', 'uvicorn', 'sqlalchemy', 'librosa', 'soundfile', 'qwen_tts', 'qwen_tts.inference', 'qwen_tts.inference.qwen3_tts_model', 'qwen_tts.inference.qwen3_tts_tokenizer', 'qwen_tts.core', 'qwen_tts.cli', 'requests', 'pkg_resources.extern', 'backend.backends.mlx_backend', 'mlx', 'mlx.core', 'mlx.nn', 'mlx_audio', 'mlx_audio.tts', 'mlx_audio.stt'] +hiddenimports = ['backend', 'backend.main', 'backend.config', 'backend.database', 'backend.models', 'backend.services.profiles', 'backend.services.history', 'backend.services.tts', 'backend.services.transcribe', 'backend.utils.platform_detect', 'backend.backends', 'backend.backends.pytorch_backend', 'backend.utils.audio', 'backend.utils.cache', 'backend.utils.progress', 'backend.utils.hf_progress', 'backend.services.cuda', 'backend.services.effects', 'backend.utils.effects', 'backend.services.versions', 'pedalboard', 'chatterbox', 'chatterbox.tts_turbo', 'chatterbox.mtl_tts', 'backend.backends.chatterbox_backend', 'backend.backends.chatterbox_turbo_backend', 'backend.backends.luxtts_backend', 'zipvoice', 'zipvoice.luxvoice', 'torch', 'transformers', 'fastapi', 'uvicorn', 'sqlalchemy', 'librosa', 'soundfile', 'qwen_tts', 'qwen_tts.inference', 'qwen_tts.inference.qwen3_tts_model', 'qwen_tts.inference.qwen3_tts_tokenizer', 'qwen_tts.core', 'qwen_tts.cli', 'requests', 'pkg_resources.extern', 'backend.backends.mlx_backend', 'mlx', 'mlx.core', 'mlx.nn', 'mlx_audio', 'mlx_audio.tts', 'mlx_audio.stt'] datas += collect_data_files('qwen_tts') datas += copy_metadata('qwen-tts') datas += copy_metadata('requests') @@ -23,6 +23,12 @@ tmp_ret = collect_all('zipvoice') datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2] tmp_ret = collect_all('linacodec') datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2] +tmp_ret = collect_all('inflect') +datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2] +tmp_ret = collect_all('perth') +datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2] +tmp_ret = collect_all('piper_phonemize') +datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2] tmp_ret = collect_all('mlx') datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2] tmp_ret = collect_all('mlx_audio') diff --git a/docs/plans/PROJECT_STATUS.md b/docs/notes/PROJECT_STATUS.md similarity index 100% rename from docs/plans/PROJECT_STATUS.md rename to docs/notes/PROJECT_STATUS.md diff --git a/mlx-test/.gitignore b/mlx-test/.gitignore deleted file mode 100644 index 420f8dc1..00000000 --- a/mlx-test/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Virtual environment -venv/ - -# Generated test files -*.wav - -# Python cache -__pycache__/ -*.pyc diff --git a/mlx-test/demo.py b/mlx-test/demo.py deleted file mode 100755 index 006da72c..00000000 --- a/mlx-test/demo.py +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env python3 -""" -Quick demo script to test MLX audio generation speed. - -Usage: - python demo.py # Use default text - python demo.py "Your custom text" # Use custom text -""" -import sys -import time -import numpy as np -import soundfile as sf -from mlx_audio.tts import load - -# Default demo text -DEFAULT_TEXT = "Hello! This is MLX audio running natively on Apple Silicon. It's incredibly fast!" - -def main(): - text = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_TEXT - - print(f"\n🎙️ MLX Audio Demo") - print(f"{'=' * 50}") - print(f"Text: \"{text}\"\n") - - # Load model - print("Loading model...", end=" ", flush=True) - start = time.time() - model = load("mlx-community/Qwen3-TTS-12Hz-1.7B-Base-bf16") - print(f"done ({time.time() - start:.1f}s)\n") - - # Generate - print("Generating audio...") - start = time.time() - - for result in model.generate(text): - # Calculate duration from audio samples - audio = np.array(result.audio) - sample_rate = result.sample_rate - duration = len(audio) / sample_rate - gen_time = float(result.processing_time_seconds) - rtf = gen_time / duration if duration > 0 else 0 - - print(f" Audio duration: {duration:.2f}s") - print(f" Generation time: {gen_time:.2f}s") - print(f" Real-time factor: {rtf:.2f}x", end="") - - if rtf < 1.0: - print(f" ⚡ ({1/rtf:.1f}x faster than real-time)") - else: - print() - - # Save audio - sf.write("test_output.wav", audio, sample_rate) - - print(f"\n✅ Saved to test_output.wav") - print(f"{'=' * 50}") - - # Play audio - print("\n🔊 Playing audio...\n") - import subprocess - subprocess.run(["afplay", "test_output.wav"]) - -if __name__ == "__main__": - main() diff --git a/mlx-test/test_tts.py b/mlx-test/test_tts.py deleted file mode 100644 index eda0ad67..00000000 --- a/mlx-test/test_tts.py +++ /dev/null @@ -1,207 +0,0 @@ -""" -Test script to validate mlx-audio can load and run Qwen3-TTS models. -""" -import sys -import time - -def test_mlx_available(): - """Step 1: Verify MLX is available and working.""" - print("=" * 60) - print("Step 1: Testing MLX availability") - print("=" * 60) - - try: - import mlx.core as mx - print(f"✓ MLX imported successfully") - print(f" Version: {mx.__version__ if hasattr(mx, '__version__') else 'unknown'}") - - # Quick compute test - a = mx.array([1.0, 2.0, 3.0]) - b = mx.array([4.0, 5.0, 6.0]) - c = a + b - print(f" Compute test: {a.tolist()} + {b.tolist()} = {c.tolist()}") - print("✓ MLX compute working\n") - return True - except Exception as e: - print(f"✗ MLX error: {e}\n") - return False - - -def test_mlx_audio_import(): - """Step 2: Verify mlx-audio modules can be imported.""" - print("=" * 60) - print("Step 2: Testing mlx-audio imports") - print("=" * 60) - - try: - import mlx_audio - print(f"✓ mlx_audio imported") - - from mlx_audio.tts import load - print(f"✓ mlx_audio.tts.load imported") - - return True - except Exception as e: - print(f"✗ Import error: {e}\n") - return False - - -def test_model_loading(): - """Step 3: Load Qwen3-TTS model (1.7B - same as voicebox uses).""" - print("=" * 60) - print("Step 3: Loading Qwen3-TTS model (1.7B)") - print("=" * 60) - print("(This will download the model on first run, ~3.4GB)") - print() - - # Model mapping - same as backend/tts.py but for MLX - # PyTorch: Qwen/Qwen3-TTS-12Hz-1.7B-Base - # MLX: mlx-community/Qwen3-TTS-12Hz-1.7B-Base-bf16 - - try: - from mlx_audio.tts import load - - start = time.time() - # Load the MLX-converted version of the same model voicebox uses - model = load("mlx-community/Qwen3-TTS-12Hz-1.7B-Base-bf16") - load_time = time.time() - start - - print(f"✓ Model loaded in {load_time:.1f}s\n") - return model - except Exception as e: - print(f"✗ Model loading error: {e}\n") - import traceback - traceback.print_exc() - return None - - -def test_generation(model): - """Step 4: Generate a short audio clip.""" - print("=" * 60) - print("Step 4: Generating test audio") - print("=" * 60) - - try: - test_text = "Hello, this is a test of MLX audio generation." - print(f" Text: \"{test_text}\"") - print(f" Model type: {type(model).__name__}") - - start = time.time() - - # mlx-audio generate() returns a generator yielding GenerationResult objects - # Each result has: audio, sample_rate, real_time_factor, etc. - audio_chunks = [] - sample_rate = 24000 - - for result in model.generate(test_text): - # result is a GenerationResult with audio and metadata - audio_chunks.append(result.audio) - sample_rate = result.sample_rate - - # Print streaming progress info - if hasattr(result, 'real_time_factor') and result.real_time_factor: - print(f" Chunk: {result.audio.shape[0]} samples, RTF: {result.real_time_factor:.2f}x") - - gen_time = time.time() - start - - # Concatenate all audio chunks - import numpy as np - audio = np.concatenate([np.array(chunk) for chunk in audio_chunks]) - - samples = len(audio) - duration = samples / sample_rate - rtf = gen_time / duration if duration > 0 else float('inf') - - print(f"✓ Audio generated:") - print(f" Samples: {samples}") - print(f" Sample rate: {sample_rate} Hz") - print(f" Duration: {duration:.2f}s") - print(f" Generation time: {gen_time:.2f}s") - print(f" Real-time factor: {rtf:.2f}x (lower is faster)") - - if rtf < 1.0: - print(f" → Faster than real-time!") - - return audio, sample_rate - except Exception as e: - print(f"✗ Generation error: {e}\n") - import traceback - traceback.print_exc() - return None, None - - -def test_save_audio(audio, sample_rate): - """Step 5: Save the generated audio to a file.""" - print("\n" + "=" * 60) - print("Step 5: Saving audio file") - print("=" * 60) - - try: - import numpy as np - import soundfile as sf - - # Audio should already be a numpy array from test_generation - audio_np = np.asarray(audio, dtype=np.float32) - - # Ensure 1D - if len(audio_np.shape) > 1: - audio_np = audio_np.squeeze() - - output_path = "test_output.wav" - sf.write(output_path, audio_np, sample_rate) - print(f"✓ Saved to: {output_path}") - - # Get file size - import os - size_kb = os.path.getsize(output_path) / 1024 - print(f" File size: {size_kb:.1f} KB\n") - - return True - except Exception as e: - print(f"✗ Save error: {e}\n") - import traceback - traceback.print_exc() - return False - - -def main(): - print("\n" + "=" * 60) - print("MLX Audio Validation Test") - print("=" * 60 + "\n") - - # Step 1: MLX - if not test_mlx_available(): - print("FAILED: MLX not available") - sys.exit(1) - - # Step 2: Imports - if not test_mlx_audio_import(): - print("FAILED: mlx-audio import failed") - sys.exit(1) - - # Step 3: Model loading - tts = test_model_loading() - if tts is None: - print("FAILED: Model loading failed") - sys.exit(1) - - # Step 4: Generation - audio, sr = test_generation(tts) - if audio is None: - print("FAILED: Audio generation failed") - sys.exit(1) - - # Step 5: Save - if not test_save_audio(audio, sr): - print("FAILED: Could not save audio") - sys.exit(1) - - print("=" * 60) - print("ALL TESTS PASSED ✓") - print("=" * 60) - print("\nMLX Audio is working correctly on this system.") - print("You can play the generated audio with: afplay test_output.wav\n") - - -if __name__ == "__main__": - main()