vocalis 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
vocalis-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,230 @@
1
+ Metadata-Version: 2.3
2
+ Name: vocalis
3
+ Version: 0.1.0
4
+ Summary: Wispr Flow-inspired AI voice dictation and code formatting CLI using Groq
5
+ Keywords: voice,dictation,whisper,groq,cli,speech-to-text,developer-tools,wispr-flow
6
+ Author: Ishan Bhat
7
+ Author-email: Ishan Bhat <ishan2003bhat@gmail.com>
8
+ License: MIT
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Topic :: Software Development
15
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
16
+ Requires-Dist: click>=8.5.1
17
+ Requires-Dist: groq>=1.7.0
18
+ Requires-Dist: numpy>=2.5.4
19
+ Requires-Dist: pynput>=1.8.2
20
+ Requires-Dist: rich>=15.0.0
21
+ Requires-Dist: scipy>=1.18.1
22
+ Requires-Dist: soundcard>=0.4.6
23
+ Requires-Python: >=3.12
24
+ Project-URL: Homepage, https://github.com/bhatishan2003/vocalis
25
+ Project-URL: Repository, https://github.com/bhatishan2003/vocalis
26
+ Project-URL: Issues, https://github.com/bhatishan2003/vocalis/issues
27
+ Description-Content-Type: text/markdown
28
+
29
+ # 🎙️ Vocalis
30
+
31
+ > **A Wispr Flow–inspired, ultra-fast AI voice dictation and code formatting CLI for developers, powered by Groq & `uv`.**
32
+
33
+ Vocalis brings the magic of modern voice dictation into your terminal and editor. Speak naturally—mumbling, hesitating, or dictating code—and Vocalis transcribes your voice in **~200ms** via Groq Whisper, removes all filler words (*"um"*, *"ah"*, *"like"*), formats your speech into clean prose or syntax-highlighted code with Llama 3.3, and copies it straight to your clipboard using **OSC 52** and native system clipboards.
34
+
35
+ ---
36
+
37
+ ## ✨ Features
38
+
39
+ - **⚡ Sub-Second Latency**: Powered by Groq's high-speed Whisper (`whisper-large-v3-turbo`) and `openai/gpt-oss-120b` (or `llama-3.3-70b-versatile`).
40
+ - **🧠 Wispr Flow AI Refinement**: Automatically cleans disfluencies, false starts, and repetitions while preserving your exact intent.
41
+ - **🎯 4 Tailored Modes**:
42
+ - `prose` (Default): Clean, professional prose for comments, docs, commit messages, and emails.
43
+ - `code`: Understands programming idioms (`camelCase`, `snake_case`, functions, indentation, syntax).
44
+ - `prompt`: Transforms spoken brainstorming into structured prompts for AI assistants (Cursor, Claude, Copilot).
45
+ - `raw`: Verbatim Whisper transcription without LLM post-processing.
46
+ - **🎙️ Zero-Lag Voice Activity Detection (VAD)**: Dynamic noise-floor calibration detects when you start and stop speaking automatically.
47
+ - **📋 Universal Clipboard Injection**: Supports **OSC 52** (works natively inside VS Code, Cursor, Neovim, tmux, Alacritty, Kitty, WezTerm) alongside Wayland (`wl-copy`) and X11 (`xclip`).
48
+ - **📦 Managed with `uv`**: Ultra-fast packaging, deterministic virtualenvs, and simple execution.
49
+
50
+ ---
51
+
52
+ ## 🚀 Quick Start
53
+
54
+ ### 1. Installation
55
+
56
+ Clone and sync the project with [`uv`](https://github.com/astral-sh/uv):
57
+
58
+ ```bash
59
+ cd vocalis
60
+ uv sync
61
+ ```
62
+
63
+ To run `vocalis` from anywhere, add an alias to your shell profile (`~/.bashrc` or `~/.zshrc`):
64
+
65
+ ```bash
66
+ alias vocalis="uv run --directory /home/ishan/vocalis vocalis"
67
+ ```
68
+
69
+ Or install it globally using `uv tool`:
70
+
71
+ ```bash
72
+ uv tool install .
73
+ ```
74
+
75
+ ---
76
+
77
+ ### 2. Configure Your Groq API Key
78
+
79
+ Set your Groq key via the CLI (saved in `~/.config/vocalis/config.json`):
80
+
81
+ ```bash
82
+ vocalis config set groq_api_key gsk_YOUR_GROQ_API_KEY
83
+ ```
84
+
85
+ Or export the standard environment variable:
86
+
87
+ ```bash
88
+ export GROQ_API_KEY="gsk_YOUR_GROQ_API_KEY"
89
+ ```
90
+
91
+ ---
92
+
93
+ ### 3. Test Your Microphone
94
+
95
+ Run the live visual VU meter to verify your audio input:
96
+
97
+ ```bash
98
+ vocalis test-mic
99
+ ```
100
+
101
+ ---
102
+
103
+ ## 💻 Usage
104
+
105
+ ### Basic Dictation
106
+ Start listening in your terminal or editor split:
107
+
108
+ ```bash
109
+ vocalis start
110
+ ```
111
+
112
+ Speak your thoughts naturally. When you finish speaking, Vocalis automatically detects silence, processes the audio through Groq, prints the result card, and copies the refined text to your clipboard.
113
+
114
+ ### One-Shot Mode (Best for Editor Keybindings)
115
+ Record a single thought, copy to clipboard, and exit:
116
+
117
+ ```bash
118
+ vocalis start --once
119
+ ```
120
+
121
+ ### Dictate Code
122
+ Converts verbal programming into clean syntax:
123
+
124
+ ```bash
125
+ vocalis start --mode code
126
+ ```
127
+ > **You say:** *"Make an async function called fetch user that takes user id as an int and returns a dictionary"*
128
+ > **Vocalis writes:** `async def fetch_user(user_id: int) -> dict:`
129
+
130
+ ### Dictate Prompts for AI Coding Assistants
131
+ Converts rambling ideas into structured prompts:
132
+
133
+ ```bash
134
+ vocalis start --mode prompt
135
+ ```
136
+
137
+ ### Continuous Loop
138
+ Keep listening for multiple thoughts:
139
+
140
+ ```bash
141
+ vocalis start --continuous
142
+ ```
143
+
144
+ ### Manual Push-to-Talk
145
+ Ignore silence detection and press **Enter** when you're done speaking:
146
+
147
+ ```bash
148
+ vocalis start --manual
149
+ ```
150
+
151
+ ### Quiet / Direct Pipe Mode
152
+ Output only the final text to stdout (ideal for shell piping):
153
+
154
+ ```bash
155
+ vocalis start --once --quiet > draft.txt
156
+ ```
157
+
158
+ ---
159
+
160
+ ## 🛠️ Editor Integration
161
+
162
+ Run `vocalis setup-editor` to view configuration snippets for your editor:
163
+
164
+ ### VS Code / Cursor Integration
165
+
166
+ 1. In `.vscode/tasks.json` (or User Tasks `Ctrl+Shift+P` → `Tasks: Open User Tasks`):
167
+ ```json
168
+ {
169
+ "version": "2.0.0",
170
+ "tasks": [
171
+ {
172
+ "label": "Vocalis: Dictate",
173
+ "type": "shell",
174
+ "command": "uv run --directory /home/ishan/vocalis vocalis start --once",
175
+ "presentation": {
176
+ "reveal": "always",
177
+ "panel": "shared",
178
+ "focus": true
179
+ },
180
+ "problemMatcher": []
181
+ }
182
+ ]
183
+ }
184
+ ```
185
+
186
+ 2. In `keybindings.json` (`Ctrl+Shift+P` → `Preferences: Open Keyboard Shortcuts (JSON)`):
187
+ ```json
188
+ [
189
+ {
190
+ "key": "ctrl+alt+v",
191
+ "command": "workbench.action.tasks.runTask",
192
+ "args": "Vocalis: Dictate"
193
+ }
194
+ ]
195
+ ```
196
+
197
+ Now pressing `Ctrl+Alt+V` in VS Code starts listening, and the moment you pause, the result is in your clipboard ready to paste!
198
+
199
+
200
+ ## ⚙️ Configuration Reference
201
+
202
+ Inspect your current configuration:
203
+
204
+ ```bash
205
+ vocalis config show
206
+ ```
207
+
208
+ Update any setting with `vocalis config set <key> <value>`:
209
+
210
+ | Key | Default | Description |
211
+ | :--- | :--- | :--- |
212
+ | `groq_api_key` | `None` | Your Groq API key |
213
+ | `default_mode` | `prose` | Default mode: `prose`, `code`, `prompt`, `raw` |
214
+ | `stt_model` | `whisper-large-v3-turbo` | Groq Whisper model |
215
+ | `llm_model` | `openai/gpt-oss-120b` | Groq LLM model (`openai/gpt-oss-120b`, `llama-3.3-70b-versatile`, etc.) |
216
+ | `silence_duration` | `0.9` | Seconds of silence before auto-finishing recording |
217
+ | `energy_threshold` | `0.012` | Voice activity detection RMS threshold |
218
+ | `language` | `en` | Speech language code (`en`, `es`, `fr`, or `auto`) |
219
+ | `auto_copy` | `True` | Automatically copy output to clipboard |
220
+ | `input_device` | `None` | Audio input device (run `vocalis list-devices`) |
221
+
222
+ ---
223
+
224
+ ## 🧪 Testing
225
+
226
+ Run the test suite with `uv`:
227
+
228
+ ```bash
229
+ uv run pytest
230
+ ```
@@ -0,0 +1,202 @@
1
+ # 🎙️ Vocalis
2
+
3
+ > **A Wispr Flow–inspired, ultra-fast AI voice dictation and code formatting CLI for developers, powered by Groq & `uv`.**
4
+
5
+ Vocalis brings the magic of modern voice dictation into your terminal and editor. Speak naturally—mumbling, hesitating, or dictating code—and Vocalis transcribes your voice in **~200ms** via Groq Whisper, removes all filler words (*"um"*, *"ah"*, *"like"*), formats your speech into clean prose or syntax-highlighted code with Llama 3.3, and copies it straight to your clipboard using **OSC 52** and native system clipboards.
6
+
7
+ ---
8
+
9
+ ## ✨ Features
10
+
11
+ - **⚡ Sub-Second Latency**: Powered by Groq's high-speed Whisper (`whisper-large-v3-turbo`) and `openai/gpt-oss-120b` (or `llama-3.3-70b-versatile`).
12
+ - **🧠 Wispr Flow AI Refinement**: Automatically cleans disfluencies, false starts, and repetitions while preserving your exact intent.
13
+ - **🎯 4 Tailored Modes**:
14
+ - `prose` (Default): Clean, professional prose for comments, docs, commit messages, and emails.
15
+ - `code`: Understands programming idioms (`camelCase`, `snake_case`, functions, indentation, syntax).
16
+ - `prompt`: Transforms spoken brainstorming into structured prompts for AI assistants (Cursor, Claude, Copilot).
17
+ - `raw`: Verbatim Whisper transcription without LLM post-processing.
18
+ - **🎙️ Zero-Lag Voice Activity Detection (VAD)**: Dynamic noise-floor calibration detects when you start and stop speaking automatically.
19
+ - **📋 Universal Clipboard Injection**: Supports **OSC 52** (works natively inside VS Code, Cursor, Neovim, tmux, Alacritty, Kitty, WezTerm) alongside Wayland (`wl-copy`) and X11 (`xclip`).
20
+ - **📦 Managed with `uv`**: Ultra-fast packaging, deterministic virtualenvs, and simple execution.
21
+
22
+ ---
23
+
24
+ ## 🚀 Quick Start
25
+
26
+ ### 1. Installation
27
+
28
+ Clone and sync the project with [`uv`](https://github.com/astral-sh/uv):
29
+
30
+ ```bash
31
+ cd vocalis
32
+ uv sync
33
+ ```
34
+
35
+ To run `vocalis` from anywhere, add an alias to your shell profile (`~/.bashrc` or `~/.zshrc`):
36
+
37
+ ```bash
38
+ alias vocalis="uv run --directory /home/ishan/vocalis vocalis"
39
+ ```
40
+
41
+ Or install it globally using `uv tool`:
42
+
43
+ ```bash
44
+ uv tool install .
45
+ ```
46
+
47
+ ---
48
+
49
+ ### 2. Configure Your Groq API Key
50
+
51
+ Set your Groq key via the CLI (saved in `~/.config/vocalis/config.json`):
52
+
53
+ ```bash
54
+ vocalis config set groq_api_key gsk_YOUR_GROQ_API_KEY
55
+ ```
56
+
57
+ Or export the standard environment variable:
58
+
59
+ ```bash
60
+ export GROQ_API_KEY="gsk_YOUR_GROQ_API_KEY"
61
+ ```
62
+
63
+ ---
64
+
65
+ ### 3. Test Your Microphone
66
+
67
+ Run the live visual VU meter to verify your audio input:
68
+
69
+ ```bash
70
+ vocalis test-mic
71
+ ```
72
+
73
+ ---
74
+
75
+ ## 💻 Usage
76
+
77
+ ### Basic Dictation
78
+ Start listening in your terminal or editor split:
79
+
80
+ ```bash
81
+ vocalis start
82
+ ```
83
+
84
+ Speak your thoughts naturally. When you finish speaking, Vocalis automatically detects silence, processes the audio through Groq, prints the result card, and copies the refined text to your clipboard.
85
+
86
+ ### One-Shot Mode (Best for Editor Keybindings)
87
+ Record a single thought, copy to clipboard, and exit:
88
+
89
+ ```bash
90
+ vocalis start --once
91
+ ```
92
+
93
+ ### Dictate Code
94
+ Converts verbal programming into clean syntax:
95
+
96
+ ```bash
97
+ vocalis start --mode code
98
+ ```
99
+ > **You say:** *"Make an async function called fetch user that takes user id as an int and returns a dictionary"*
100
+ > **Vocalis writes:** `async def fetch_user(user_id: int) -> dict:`
101
+
102
+ ### Dictate Prompts for AI Coding Assistants
103
+ Converts rambling ideas into structured prompts:
104
+
105
+ ```bash
106
+ vocalis start --mode prompt
107
+ ```
108
+
109
+ ### Continuous Loop
110
+ Keep listening for multiple thoughts:
111
+
112
+ ```bash
113
+ vocalis start --continuous
114
+ ```
115
+
116
+ ### Manual Push-to-Talk
117
+ Ignore silence detection and press **Enter** when you're done speaking:
118
+
119
+ ```bash
120
+ vocalis start --manual
121
+ ```
122
+
123
+ ### Quiet / Direct Pipe Mode
124
+ Output only the final text to stdout (ideal for shell piping):
125
+
126
+ ```bash
127
+ vocalis start --once --quiet > draft.txt
128
+ ```
129
+
130
+ ---
131
+
132
+ ## 🛠️ Editor Integration
133
+
134
+ Run `vocalis setup-editor` to view configuration snippets for your editor:
135
+
136
+ ### VS Code / Cursor Integration
137
+
138
+ 1. In `.vscode/tasks.json` (or User Tasks `Ctrl+Shift+P` → `Tasks: Open User Tasks`):
139
+ ```json
140
+ {
141
+ "version": "2.0.0",
142
+ "tasks": [
143
+ {
144
+ "label": "Vocalis: Dictate",
145
+ "type": "shell",
146
+ "command": "uv run --directory /home/ishan/vocalis vocalis start --once",
147
+ "presentation": {
148
+ "reveal": "always",
149
+ "panel": "shared",
150
+ "focus": true
151
+ },
152
+ "problemMatcher": []
153
+ }
154
+ ]
155
+ }
156
+ ```
157
+
158
+ 2. In `keybindings.json` (`Ctrl+Shift+P` → `Preferences: Open Keyboard Shortcuts (JSON)`):
159
+ ```json
160
+ [
161
+ {
162
+ "key": "ctrl+alt+v",
163
+ "command": "workbench.action.tasks.runTask",
164
+ "args": "Vocalis: Dictate"
165
+ }
166
+ ]
167
+ ```
168
+
169
+ Now pressing `Ctrl+Alt+V` in VS Code starts listening, and the moment you pause, the result is in your clipboard ready to paste!
170
+
171
+
172
+ ## ⚙️ Configuration Reference
173
+
174
+ Inspect your current configuration:
175
+
176
+ ```bash
177
+ vocalis config show
178
+ ```
179
+
180
+ Update any setting with `vocalis config set <key> <value>`:
181
+
182
+ | Key | Default | Description |
183
+ | :--- | :--- | :--- |
184
+ | `groq_api_key` | `None` | Your Groq API key |
185
+ | `default_mode` | `prose` | Default mode: `prose`, `code`, `prompt`, `raw` |
186
+ | `stt_model` | `whisper-large-v3-turbo` | Groq Whisper model |
187
+ | `llm_model` | `openai/gpt-oss-120b` | Groq LLM model (`openai/gpt-oss-120b`, `llama-3.3-70b-versatile`, etc.) |
188
+ | `silence_duration` | `0.9` | Seconds of silence before auto-finishing recording |
189
+ | `energy_threshold` | `0.012` | Voice activity detection RMS threshold |
190
+ | `language` | `en` | Speech language code (`en`, `es`, `fr`, or `auto`) |
191
+ | `auto_copy` | `True` | Automatically copy output to clipboard |
192
+ | `input_device` | `None` | Audio input device (run `vocalis list-devices`) |
193
+
194
+ ---
195
+
196
+ ## 🧪 Testing
197
+
198
+ Run the test suite with `uv`:
199
+
200
+ ```bash
201
+ uv run pytest
202
+ ```
@@ -0,0 +1,56 @@
1
+ [project]
2
+ name = "vocalis"
3
+ version = "0.1.0"
4
+ description = "Wispr Flow-inspired AI voice dictation and code formatting CLI using Groq"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = [
8
+ "click>=8.5.1",
9
+ "groq>=1.7.0",
10
+ "numpy>=2.5.4",
11
+ "pynput>=1.8.2",
12
+ "rich>=15.0.0",
13
+ "scipy>=1.18.1",
14
+ "soundcard>=0.4.6",
15
+ ]
16
+ keywords = [
17
+ "voice",
18
+ "dictation",
19
+ "whisper",
20
+ "groq",
21
+ "cli",
22
+ "speech-to-text",
23
+ "developer-tools",
24
+ "wispr-flow",
25
+ ]
26
+ classifiers = [
27
+ "Development Status :: 4 - Beta",
28
+ "Environment :: Console",
29
+ "Intended Audience :: Developers",
30
+ "Programming Language :: Python :: 3",
31
+ "Programming Language :: Python :: 3.12",
32
+ "Topic :: Software Development",
33
+ "Topic :: Multimedia :: Sound/Audio :: Speech",
34
+ ]
35
+
36
+ [[project.authors]]
37
+ name = "Ishan Bhat"
38
+ email = "ishan2003bhat@gmail.com"
39
+
40
+ [project.license]
41
+ text = "MIT"
42
+
43
+ [project.urls]
44
+ Homepage = "https://github.com/bhatishan2003/vocalis"
45
+ Repository = "https://github.com/bhatishan2003/vocalis"
46
+ Issues = "https://github.com/bhatishan2003/vocalis/issues"
47
+
48
+ [project.scripts]
49
+ vocalis = "vocalis:main"
50
+
51
+ [build-system]
52
+ requires = ["uv_build>=0.12.20,<0.13.0"]
53
+ build-backend = "uv_build"
54
+
55
+ [dependency-groups]
56
+ dev = ["pytest>=9.2.0"]
@@ -0,0 +1,47 @@
1
+ [project]
2
+ name = "vocalis"
3
+ version = "0.1.0"
4
+ description = "Wispr Flow-inspired AI voice dictation and code formatting CLI using Groq"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Ishan Bhat", email = "ishan2003bhat@gmail.com" }
8
+ ]
9
+ requires-python = ">=3.12"
10
+ dependencies = [
11
+ "click>=8.5.1",
12
+ "groq>=1.7.0",
13
+ "numpy>=2.5.4",
14
+ "pynput>=1.8.2",
15
+ "rich>=15.0.0",
16
+ "scipy>=1.18.1",
17
+ "soundcard>=0.4.6",
18
+ ]
19
+
20
+ license = { text = "MIT" }
21
+ keywords = ["voice", "dictation", "whisper", "groq", "cli", "speech-to-text", "developer-tools", "wispr-flow"]
22
+ classifiers = [
23
+ "Development Status :: 4 - Beta",
24
+ "Environment :: Console",
25
+ "Intended Audience :: Developers",
26
+ "Programming Language :: Python :: 3",
27
+ "Programming Language :: Python :: 3.12",
28
+ "Topic :: Software Development",
29
+ "Topic :: Multimedia :: Sound/Audio :: Speech",
30
+ ]
31
+
32
+ [project.urls]
33
+ Homepage = "https://github.com/bhatishan2003/vocalis"
34
+ Repository = "https://github.com/bhatishan2003/vocalis"
35
+ Issues = "https://github.com/bhatishan2003/vocalis/issues"
36
+
37
+ [project.scripts]
38
+ vocalis = "vocalis:main"
39
+
40
+ [build-system]
41
+ requires = ["uv_build>=0.12.20,<0.13.0"]
42
+ build-backend = "uv_build"
43
+
44
+ [dependency-groups]
45
+ dev = [
46
+ "pytest>=9.2.0",
47
+ ]
@@ -0,0 +1,6 @@
1
+ """Vocalis: Wispr Flow-inspired AI voice dictation CLI."""
2
+
3
+ from vocalis.cli import cli, main
4
+
5
+ __version__ = "0.1.0"
6
+ __all__ = ["cli", "main", "__version__"]
@@ -0,0 +1,7 @@
1
+ """Audio recording, processing, and sound effects module."""
2
+
3
+ from vocalis.audio.chime import ChimePlayer
4
+ from vocalis.audio.recorder import AudioRecorder
5
+ from vocalis.audio.vad import SpeechState, VADDetector
6
+
7
+ __all__ = ["AudioRecorder", "ChimePlayer", "SpeechState", "VADDetector"]
@@ -0,0 +1,77 @@
1
+ """Non-blocking audio cues and earcons for Vocalis."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import io
6
+ import shutil
7
+ import subprocess
8
+ import threading
9
+ from typing import Optional
10
+
11
+ import numpy as np
12
+ from scipy.io import wavfile
13
+
14
+
15
+ class ChimePlayer:
16
+ """Generates and plays lightweight audio chimes for voice state transitions."""
17
+
18
+ def __init__(self, enabled: bool = True) -> None:
19
+ self.enabled = enabled
20
+ self._start_wav: Optional[bytes] = None
21
+ self._done_wav: Optional[bytes] = None
22
+ if self.enabled:
23
+ self._prepare_chimes()
24
+
25
+ def _generate_tone(self, freq: float, duration: float, volume: float = 0.12) -> bytes:
26
+ """Synthesize a soft sine wave chime with smooth cosine envelope."""
27
+ sample_rate = 16000
28
+ num_samples = int(sample_rate * duration)
29
+ t = np.linspace(0, duration, num_samples, endpoint=False)
30
+
31
+ # Smooth bell envelope (raised cosine) to eliminate clicks
32
+ envelope = np.sin(np.pi * t / duration)
33
+ signal = (envelope * volume * np.sin(2 * np.pi * freq * t) * 32767.0).astype(np.int16)
34
+
35
+ buf = io.BytesIO()
36
+ wavfile.write(buf, sample_rate, signal)
37
+ return buf.getvalue()
38
+
39
+ def _prepare_chimes(self) -> None:
40
+ """Pre-generate in-memory WAV bytes so playback has zero computational latency."""
41
+ try:
42
+ # Start chime: Pleasant high A5 (880 Hz, 35ms)
43
+ self._start_wav = self._generate_tone(880.0, 0.035, volume=0.10)
44
+ # Done chime: Bright C6 (1046.5 Hz, 45ms)
45
+ self._done_wav = self._generate_tone(1046.5, 0.045, volume=0.10)
46
+ except Exception:
47
+ self.enabled = False
48
+
49
+ def _play_raw(self, wav_bytes: Optional[bytes]) -> None:
50
+ """Play WAV audio in a subprocess without blocking the main thread."""
51
+ if not self.enabled or not wav_bytes:
52
+ return
53
+
54
+ def worker():
55
+ for cmd in (["aplay", "-q"], ["pw-play", "-"]):
56
+ if shutil.which(cmd[0]):
57
+ try:
58
+ proc = subprocess.Popen(
59
+ cmd,
60
+ stdin=subprocess.PIPE,
61
+ stdout=subprocess.DEVNULL,
62
+ stderr=subprocess.DEVNULL,
63
+ )
64
+ proc.communicate(input=wav_bytes, timeout=0.6)
65
+ return
66
+ except Exception:
67
+ continue
68
+
69
+ threading.Thread(target=worker, daemon=True).start()
70
+
71
+ def play_start(self) -> None:
72
+ """Play cue when speech starts."""
73
+ self._play_raw(self._start_wav)
74
+
75
+ def play_done(self) -> None:
76
+ """Play cue when processing completes and text is typed."""
77
+ self._play_raw(self._done_wav)