Skip to content

feat(desktop): import local Pocket voices - #3259

Draft
johnmatthewtennant wants to merge 3 commits into
jtennant/pocket-tts-settings-v1from
jtennant/pocket-voice-import
Draft

feat(desktop): import local Pocket voices#3259
johnmatthewtennant wants to merge 3 commits into
jtennant/pocket-tts-settings-v1from
jtennant/pocket-voice-import

Conversation

@johnmatthewtennant

@johnmatthewtennant johnmatthewtennant commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Context

Pocket TTS currently offers only bundled reference voices. People need a local, private way to add another voice without sending audio through the renderer or a cloud service.

Summary

This adds a minimal Pocket voice import flow to the existing Voice settings card. Buzz opens the native macOS file picker, validates and canonicalizes the selected WAV in Rust, stores it in app data under a content-derived identity, selects it, and lets the user safely delete it later.

The import and audio lifecycle is exercised directly through the reusable buzz-voice crate. The deterministic path imports a real WAV, reloads persisted state, synthesizes and inspects PCM with the selected voice, interrupts synthesis without releasing partial audio, deletes the voice, and proves the exact Mary fallback. A BlackHole loopback test also plays generated PCM through a real Core Audio device and captures a non-silent signal.

Changes

  • Adds a native WAV picker and validates mono PCM or float WAV files between 2 and 30 seconds.
  • Canonicalizes accepted audio to mono 32 kHz PCM16 before hashing and storage.
  • Uses stable pocket:imported:<sha256> voice identities and content-addressed app-data files.
  • Keeps absolute file paths inside the native process and exposes only voice metadata to React.
  • Moves reusable import, registry, persistence, deletion, and PCM inspection into buzz-voice; the Tauri module is a thin native-picker adapter.
  • Adds local imported voices to Pocket preview and live huddle playback.
  • Adds Add voice and delete controls to Voice settings, with Mary as the deterministic deletion fallback.
  • Preserves bundled voices if optional import metadata is unreadable and keeps failed deletion retryable.
  • Covers native-picker cancellation and invalid or unsupported files without changing the selected voice or emitting audio.

Related issue

None found.

Testing

Passed:

  • cargo test -p buzz-voice: 15 passed; the two hardware/model tests compiled and remained explicitly ignored by default.
  • Installed Pocket model objective workflow: imported voice persistence and relaunch, selected-voice synthesis, non-silent PCM inspection, interrupted partial-PCM discard, empty-input silence, deletion, and Mary fallback all passed.
  • BlackHole 2ch loopback: generated Pocket PCM played through the named Core Audio device and the input captured 100,534 non-silent samples at 48 kHz.
  • cargo test --manifest-path desktop/src-tauri/Cargo.toml huddle:: --lib: 109 passed, including the thin assistant-message routing test.
  • cd desktop && pnpm run build:e2e.
  • cd desktop && pnpm exec playwright test tests/e2e/voice-settings.spec.ts --project=smoke: 5 passed, including import/select/delete, imported and Mary preview routing, picker cancellation, and invalid input.
  • Full pre-push desktop checks: 3,732 TypeScript tests and 1,827 native desktop tests passed.
  • Rust and Tauri formatting, warning-as-error Clippy, TypeScript typecheck, Biome, file-size checks, and diff checks.
  • Focused Timeless Comments and Dead Code review found no changes to apply. Stacksmith's cancellation-layer finding is resolved by keeping the synthesis API and worker contract in the parent settings layer; this diff contains only importer-specific integration.

Only subjective listening quality remains for manual validation. The native picker boundary, objective signal production, huddle routing, deletion fallback, interruption, and unintended-audio conditions are automated.

Screenshots

Final Voice settings surface with the bundled and imported Pocket voice selector, preview, and local Add voice action:

Pocket voice importer settings

Reviewer-reproducible examples

The commands below use the checked-in Marius WAV as the import fixture. Launch Buzz once and install the Pocket TTS model before running them; the default model directory is ~/.buzz/models/pocket-tts.

1. Exercise the objective import and audio lifecycle

BUZZ_POCKET_MODEL_DIR="$HOME/.buzz/models/pocket-tts" \
BUZZ_VOICE_EVIDENCE_DIR="$PWD/desktop/test-results/voice-audio" \
cargo test -p buzz-voice --test pocket_import_audio \
  objective_import_synthesis_delete_and_mary_fallback \
  -- --ignored --exact --nocapture

Observed output excerpt:

{
  "persistence": "reloaded",
  "interruption": "partial PCM discarded",
  "emptyInputSamples": 0,
  "afterDelete": "pocket:mary",
  "importedPreview": {
    "sampleRate": 24000,
    "samples": 59520,
    "nonSilentSamples": 29477
  },
  "maryFallbackPreview": {
    "sampleRate": 24000,
    "samples": 78720,
    "nonSilentSamples": 57285
  }
}

The test writes imported-preview.wav and mary-fallback-preview.wav under desktop/test-results/voice-audio for optional subjective listening.

2. Play and capture generated audio through BlackHole

With the free BlackHole 2ch Core Audio device installed:

BUZZ_POCKET_MODEL_DIR="$HOME/.buzz/models/pocket-tts" \
BUZZ_VOICE_AUDIO_DEVICE="BlackHole 2ch" \
cargo test -p buzz-voice --test pocket_import_audio \
  blackhole::blackhole_playback_captures_generated_non_silent_pcm \
  -- --ignored --exact --nocapture

Observed output excerpt:

{
  "device": "BlackHole 2ch",
  "captured": {
    "sampleRate": 48000,
    "samples": 265216,
    "nonSilentSamples": 100534,
    "peak": 0.04569,
    "rms": 0.00406
  }
}

The test selects BlackHole by name and does not change the system default input or output.

3. Exercise the desktop controls and native-picker boundary outcomes

cd desktop
pnpm run build:e2e
pnpm exec playwright test tests/e2e/voice-settings.spec.ts --project=smoke

Observed result:

5 passed

The UI suite proves imported selection and Preview routing, deletion and Mary Preview routing, native-picker cancellation with no audio command, and invalid or unsupported input with no selection change. Real WAV validation, persistence, synthesis, playback, capture, interruption, deletion, and fallback are covered by the two crate-level paths above.

johnmatthewtennant added a commit that referenced this pull request Jul 28, 2026
@johnmatthewtennant

Copy link
Copy Markdown
Contributor Author

🤖 Pocket voice importer settings. Imported and bundled Pocket voices share the local Voice settings selector, with private on-device WAV import available through Add voice.

pocket-voices

@johnmatthewtennant
johnmatthewtennant force-pushed the jtennant/pocket-tts-settings-v1 branch from 4891a92 to c118f5a Compare July 28, 2026 04:48
@johnmatthewtennant
johnmatthewtennant force-pushed the jtennant/pocket-voice-import branch from c06a505 to 3fb3c74 Compare July 28, 2026 04:54
Signed-off-by: John Tennant <jtennant@squareup.com>
Signed-off-by: John Tennant <jtennant@squareup.com>
@johnmatthewtennant
johnmatthewtennant force-pushed the jtennant/pocket-voice-import branch from 3fb3c74 to c5832db Compare July 28, 2026 05:02
Signed-off-by: John Tennant <jtennant@squareup.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant