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
This commit is contained in:
James Pine
2026-03-16 06:15:04 -07:00
parent 1b2d492398
commit 5933cba8e9
9 changed files with 591 additions and 408 deletions
@@ -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]
<One strong opening paragraph: what this release is about and why it matters.
Tie it to concrete shipped changes. No vague hype.>
<One paragraph on major technical shifts, if applicable.>
### <Feature/Theme Group>
- Bullet points with specifics
- Reference PRs where available: ([#123](https://github.com/jamiepine/voicebox/pull/123))
### <Another Group>
- ...
### 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.
+124
View File
@@ -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
<the content that was in [Unreleased]>
```
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 <patch|minor|major>
```
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)`
+366 -69
View File
@@ -1,96 +1,393 @@
<!-- This file is compiled automatically during the release workflow. -->
<!-- Do not edit manually — your changes will be overwritten. -->
<!-- To update the draft: ask the agent to use the draft-release-notes skill. -->
<!-- To finalize a release: ask the agent to use the release-bump skill. -->
# 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 — 1005,000 chars (default 800)
- Crossfade slider — 0200ms (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
-58
View File
@@ -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*
+7 -1
View File
@@ -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')
-9
View File
@@ -1,9 +0,0 @@
# Virtual environment
venv/
# Generated test files
*.wav
# Python cache
__pycache__/
*.pyc
-64
View File
@@ -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()
-207
View File
@@ -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()