feat(voice): add VoxCPM2 (OpenBMB) as a fifth TTS backend candidate [spec 02] - #10
Merged
Conversation
…spec 02] Adds VoxCPM2 (mlx-community/VoxCPM2-8bit) to the MLX backend registry as a post-L0 blind-A/B candidate. The thin MlxAudioBackend + profile seam means the whole model is one PROFILES row — smoke-verified end-to-end through the standard SynthesisRequest -> wav path: loads (mlx-audio #649 does not block), 48 kHz native, measured RTF ~=1.6 on M3 Pro/8bit (slower than real-time -> a quality-reference / pre-generated candidate, not the real-time default). Also fixes a registry-drift bug the review caught: the CLI --voice choices were hardcoded and rejected 'voxcpm2' even though build_voice() accepted it. Now derived from PROFILES so future rows are CLI-selectable automatically, guarded by a new regression test. - src: PROFILES row; --voice choices derived from the registry - spec 02 §3.3/§4/§5/§6 + DESIGN shortlist + README/pyproject aligned - tests (test-first): registry/integration params + CLI-accepts-every-voice guard Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
wine-fall
force-pushed
the
zachg-0704--add-voxcpm2
branch
from
July 4, 2026 15:01
a2b0b89 to
6385617
Compare
wine-fall
added a commit
that referenced
this pull request
Jul 4, 2026
Codifies the murmur PR workflow so the check_pr.py title/description gate is run LOCALLY and required green before pushing — the failure mode that just cost a CI round-trip on #10 (body linked "spec 02" but no on-disk specs/*.md path). Captures the session's hard-won specifics: org PR rules (branch naming, base main, no direct push to master, squash merge, required AI coding brief), what check_pr.py enforces per commit-type, and the gh-pr-edit-silently-no-ops workaround (PATCH the body via gh api REST instead). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
wine-fall
added a commit
that referenced
this pull request
Jul 4, 2026
Codifies the murmur PR workflow so the check_pr.py title/description gate is run LOCALLY and required green before pushing — the failure mode that just cost a CI round-trip on #10 (body linked "spec 02" but no on-disk specs/*.md path). Captures the session's hard-won specifics: org PR rules (branch naming, base main, no direct push to master, squash merge, required AI coding brief), what check_pr.py enforces per commit-type, and the gh-pr-edit-silently-no-ops workaround (PATCH the body via gh api REST instead). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements
specs/spec02/02-voice-provider.md(also touchesspecs/DESIGN.md).Summary
Adds VoxCPM2 (
mlx-community/VoxCPM2-8bit) to the MLX voice-backend registry as a post-L0 blind-A/B candidate (specs/spec02/02-voice-provider.md§3.3). The thinMlxAudioBackend+MlxProfileseam means the whole model is onePROFILESrow — the standardizedSynthesisRequest → wavlayer does the rest.Smoke-verified end-to-end through the production path (
build_backend → load → warm → synthesize):model_type: voxcpm2) does not block on the installed versionBug fixed (caught by closing code-review)
The CLI
--voiceargparsechoiceswere hardcoded and rejectedvoxcpm2even thoughbuild_voice()accepted it (unit tests bypassed argparse, so they missed it). Fixed at altitude —choicesnow derived fromPROFILESso future rows are CLI-selectable automatically — and guarded by a new regression test.Changes
PROFILESrow;--voicechoices derived from the registryspecs/spec02/02-voice-provider.md§3.3/§4/§5/§6 +specs/DESIGN.mdshortlist +README/pyprojectalignedtest_cli_voice_flag_accepts_every_registered_voiceTest plan
pytest -m "not integration"→ 101 passed; ruff + format cleanpytest -m integrationnow includesvoxcpm2(needs[tts-mlx]+ weights)AI coding brief
Original request — While evaluating OpenBMB's new TTS for the murmur companion-radio project, confirmed VoxCPM2 supports MLX and compared it against the already-installed candidates (Spark/Qwen3/Chatterbox/Dia). Asked to pull the model via HuggingFace and add it to the candidate pool on a new branch. Why: widen the blind-A/B voice pool with a higher-fidelity (48 kHz, tokenizer-free) contender.
Manual interventions — The human pushed back hard on "you just added comments — does the new model's I/O actually match the standardized layer?", which drove a real end-to-end smoke (proving the seam conforms, measuring RTF 1.61, and clearing the #649 load risk) instead of taking the design on faith. 8bit-over-4bit was settled in discussion (quality reference for the A/B; 18 GB fits). The human then said to ship, then to open the PR.
Retro — Two things would have shipped this faster: (1) the CLI registry-drift bug was foreseeable — prompting "add the model and make sure every entry point (CLI choices, config, docs) derives from the registry, not a hardcoded list" up front would have pre-empted it; (2) the first
huggingface-cli downloadsilently no-op'd (deprecated in hub 1.21) — a one-line "verify the weights landed in the cache before proceeding" check would have caught it a step earlier.🤖 Generated with Claude Code