cpal 0.15 uses ALSA as its Linux backend, which does not expose
PulseAudio/PipeWire monitor sources. The previous approach searched
for 'monitor' in cpal device names, which never matched on most
Linux systems, silently falling back to the microphone input.
This fix:
- Detects the correct monitor source via 'pactl get-default-sink'
and 'pactl list short sources'
- Sets PULSE_SOURCE env var before cpal initialization so PulseAudio's
ALSA plugin routes the default input through the monitor
- Preserves the original name-based search as fallback when pactl is
unavailable
- No new dependencies added
Tested on PipeWire 1.0.5 with Realtek ALC897 (HD-Audio Generic).
The HTTP /watchdog/disable request races with process exit on Windows,
causing the watchdog to kill the server before the request arrives.
Added a .keep-running sentinel file as a reliable fallback:
- Tauri writes the file to data_dir before sending the HTTP request
- The watchdog checks for it during the grace period after detecting
parent death
- The file is removed after being read to avoid stale state
This approach works regardless of HTTP timing because file writes
complete synchronously before the Tauri process exits.
Fixes#372
Co-authored-by: Matt Van Horn <[email protected]>
The cpal Stream was created and play() called but then immediately
dropped when play_to_device() returned. When a cpal Stream is dropped,
audio output stops immediately. This caused silent playback.
Fix: add a spin-wait loop that holds the Stream in scope until all
samples have been consumed (or stop_flag is set).
- Rust: Replace fragile body.contains("status") with proper JSON
deserialization validating status=="healthy", model_loaded (bool),
and gpu_available (bool) to prevent misidentifying non-Voicebox services
- Frontend: Validate health response has Voicebox-specific fields before
marking server as ready during fallback polling
- Frontend: Discriminate port-in-use errors (poll for external server) from
real startup failures (missing sidecar, signing issues) — surface errors
immediately with a startupError state and Retry button in the UI
- Frontend: Set explicit startup-error state when 2-minute polling timeout
expires so the loading screen shows actionable feedback instead of hanging
- Architecture: Extract QueryClient to standalone side-effect-free module
(lib/queryClient.ts) to decouple serverStore from React bootstrap entrypoint
Two fixes for issue #312:
1. GUI stuck on loading screen when backend is already running externally
(e.g. via python/uvicorn/Docker):
- Rust: add HTTP health check fallback when the process on the port
doesn't have 'voicebox' in its name. If /health responds with a
valid Voicebox response, reuse the server instead of erroring.
- Frontend: when startServer() fails, fall back to polling the
health endpoint every 2s instead of permanently blocking.
2. No data refresh when switching server URLs in settings:
- serverStore.setServerUrl() now invalidates all React Query caches
when the URL actually changes, so profiles/history/models/stories
are re-fetched from the new server.
- Export queryClient from main.tsx for store-level cache invalidation.
Fixes#312
Switch CUDA builds from PyInstaller --onefile to --onedir and split the
output into two separately versioned archives:
1. Server core (~200-400MB) — versioned with the app, redownloaded on
every app update
2. CUDA libs (~2GB) — versioned independently (cu126-v1), only
redownloaded when the CUDA toolkit or torch version changes
This eliminates the ~2.4GB full redownload on every version bump.
After initial setup, most app updates only need ~200-400MB.
Closes#297
build.rs now generates voicebox.icns via sips + iconutil alongside the
existing actool Assets.car compilation. On non-macOS, empty stub files
are created so Tauri's resource bundler doesn't fail on missing paths.
- Rename Server tab to Settings with horizontal sub-tab navigation (General, Generation, GPU, Logs, Changelog)
- All sub-tabs are proper routes under /settings/* with /server redirect for backwards compat
- General: connection settings, link cards (docs + discord), API reference card, app updates
- Generation: auto-chunking, crossfade, normalize, autoplay as SettingRow components
- GPU: info card with platform-aware icons (Apple logo for MPS), CUDA management, explainer text
- Logs: real-time server log viewer piped from Tauri sidecar via event system (Tauri-only)
- Changelog: parsed from CHANGELOG.md at build time via Vite virtual module plugin
- New reusable SettingRow/SettingSection components for consistent settings layout
- New Toggle (switch) UI component replacing checkboxes in settings
- Toast viewport now offsets when audio player is open
- Sidebar stays active on settings sub-routes (fuzzy matching)
Critical:
- Remove dead backend.utils.validation PyInstaller hidden import
- Fix story_items table rebuild to preserve track/trim/version columns
- Guard cache migration against same source/destination path
- Fix regeneration audio overwrite (use random uuid suffix per take)
Major:
- Engine selector: validate language on Qwen switch, clear stale modelSize
- Sync language validation regex between profile create and generate (22 langs)
- Guard CUDA download against duplicate concurrent requests
- Only set model_size for engines that support multiple sizes
- Fix 404 swallowed by generic except in history export
- Validate audio_path before FileResponse in export-audio
- Transcription: stream uploads in 1MB chunks, use robust cache check,
call complete_download() on Whisper download success
- Set clean version as default when effects chain validation fails
- Return explicit error when Windows port occupied by non-voicebox process
On Windows, Voicebox shelled out to netstat.exe on startup to check for
existing server processes. On systems with corrupted DLLs, netstat fails
with 0xc0000142, causing an infinite loading loop.
Replace with:
- TcpStream::connect_timeout() for port-in-use checks (pure Rust)
- PowerShell Get-NetTCPConnection for port-to-PID lookup (built-in cmdlet)
- tasklist for process name verification (unchanged)
Closes#277
- Replace Qwen-specific copy with multi-engine messaging across hero, meta, and features
- Add GitHub star count fetched server-side via /api/stars with Spacedrive-style navbar badge
- Replace 'Why Voicebox exists' section with model cards for all 4 TTS engines
- Enable Linux download card (was 'Coming soon')
- Update GPU support copy to include ROCm, Intel Arc, DirectML
- Add Voice Creator section with animated 3-tab UI (upload, mic, system audio) and waveform background
- Make ControlUI responsive: horizontal scroll cards on mobile, stacked layout, scroll-to-active profile
- Fix iOS Safari audio autoplay (unlock AudioContext on user gesture)
- Fix hero logo square background with mix-blend-lighten
- Remove generation length green coloring, use gray with accent highlights
- Comment out grain overlay (visible tile seams)
- Remove player close button, stack waveform above controls on mobile
- Fixed-height profile cards (143px) with space between badges and buttons
Generations now return immediately with a 'generating' status and appear
in history right away. TTS runs in a serial background queue to avoid
GPU contention. Users can kick off multiple generations without blocking.
- Async POST /generate creates DB record immediately, queues TTS work
- Serial generation queue prevents concurrent GPU access (Metal/CUDA/CPU)
- SSE endpoint GET /generate/{id}/status for real-time completion tracking
- Retry endpoint POST /generate/{id}/retry for failed generations
- Store engine and model_size on generation records for retry support
- History cards show animated loader (react-loaders) for generating/playing
- Failed generations show retry button instead of actions menu
- Model downloads happen inline in the queue instead of rejecting with 202
- Stale 'generating' records marked as failed on server startup
- Autoplay on generate setting (default: on)
- Show engine name on generation cards
- Remove sidebar generation spinner
- Checkbox alignment fix in settings
- Add model folder migration with byte-level progress tracking (backend + UI)
- Custom models directory support via VOICEBOX_MODELS_DIR env var passed to sidecar
- Hardcoded model descriptions displayed in model detail cards
- Open model folder button in storage location row
- Remove 'not downloaded' badge from model cards
- Fix server settings scroll offset for audio player
- Fix shell open permission to allow file paths
- Add normalize toggle to generation settings
Cherry-picked and adapted from PR #89 and #214:
- Linux audio capture via PulseAudio/PipeWire monitor sources (cpal)
- AMD ROCm GPU support: HSA_OVERRIDE_GFX_VERSION env var, ROCm detection
- Whisper Turbo model (openai/whisper-large-v3-turbo) in all endpoints
- Cleaner Whisper language handling via generate_kwargs
- tauri::async_runtime::spawn fix to prevent panic on app shutdown
- Enable Linux (ubuntu-22.04) in release CI matrix
- New ChatterboxTTSBackend wrapping ChatterboxMultilingualTTS (ResembleAI/chatterbox)
- Supports 23 languages including Hebrew, forces CPU on macOS (MPS issue)
- Monkey-patches torch.load for CPU loading, forces eager attention for compatibility
- trim_tts_output utility cuts trailing silence/hallucination from Chatterbox output
- Full engine integration: /generate, /generate/stream, model status/download/delete
- Hebrew (he) added to supported languages in frontend and backend validation
- Single flat model dropdown extended with Chatterbox option in both generation UIs
- ModelManagement UI groups LuxTTS and Chatterbox under 'Other Voice Models' section
Add the ability to download a CUDA-enabled backend binary (~2.4 GB) and
swap it in via a backend-only restart, solving the #1 user pain point
(19 open 'GPU not detected' issues caused by GitHub's 2 GB asset limit).
Backend:
- cuda_download.py: download from R2 (primary) or GitHub split-parts
(fallback), SHA-256 verification, atomic writes, progress via SSE
- 4 new endpoints: GET/POST/DELETE /backend/cuda-*, GET cuda-progress
- server.py: --version flag, auto-detect variant from binary name
- build_binary.py: --cuda flag for CUDA PyInstaller builds
- split_binary.py: split large binaries into <2GB GitHub Release assets
- CI workflow for building CUDA binary
Tauri:
- restart_server command (stop -> wait -> start)
- start_server prefers CUDA binary from {data_dir}/backends/ if present
- Version mismatch check: runs --version before launching CUDA binary
Frontend:
- GpuAcceleration component: download, progress, restart, switch, delete
- API client + types for CUDA status and management
- Platform lifecycle: restartServer() on Tauri/Web
- Aggressive 1s health polling during restart for fast reconnection
Address CodeRabbit review feedback:
- Makefile: only set WEBKIT_DISABLE_DMABUF_RENDERER=1 when running on
Linux with an NVIDIA GPU detected via lspci
- main.rs: validate webview origin before auto-granting microphone
permission — only allow for trusted local origins (tauri://, localhost,
127.0.0.1)
Co-Authored-By: Claude Opus 4.6 <[email protected]>
WebKitGTK denies getUserMedia by default. This adds webkit2gtk as a
Linux dependency and configures the webview to enable media streams
and auto-grant UserMediaPermissionRequest for microphone access.
Co-Authored-By: Claude Opus 4.6 <[email protected]>