pi-voicekit 0.1.1 → 0.1.3
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.
- package/README.md +6 -6
- package/extensions/voice/audio-tool.ts +16 -0
- package/extensions/voice.ts +84 -75
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# pi-voicekit
|
|
4
4
|
|
|
5
|
-
> **Community continuation of [`codexstar69/pi-listen`](https://github.com/codexstar69/pi-listen)** (
|
|
5
|
+
> **Community continuation of [`codexstar69/pi-listen`](https://github.com/codexstar69/pi-listen)** (upstream, MIT — dormant since v7.2.2 in May 2026).
|
|
6
6
|
> Not affiliated with the original author. Old name: `pi-listen`.
|
|
7
7
|
|
|
8
8
|
<p align="center">
|
|
@@ -109,9 +109,9 @@ Toggle between Deepgram (cloud, live streaming) and Local (offline, batch mode).
|
|
|
109
109
|
|
|
110
110
|
### Models — browse, search, install
|
|
111
111
|
|
|
112
|
-
<img src="https://raw.githubusercontent.com/CyFeng16/pi-voicekit/main/assets/settings-models.png" alt="Models tab — browse
|
|
112
|
+
<img src="https://raw.githubusercontent.com/CyFeng16/pi-voicekit/main/assets/settings-models.png" alt="Models tab — browse 21 models with accuracy/speed ratings" width="600" />
|
|
113
113
|
|
|
114
|
-
Browse
|
|
114
|
+
Browse 21 models from Parakeet, Whisper, Moonshine, SenseVoice, GigaAM, Paraformer, and Qwen3. Each model shows accuracy and speed ratings (●●●●○/●●●●○), fitness badges, and download status. Fuzzy search to find models fast. Press Enter to activate and download.
|
|
115
115
|
|
|
116
116
|
### Downloaded — manage installed models
|
|
117
117
|
|
|
@@ -189,7 +189,7 @@ in front):
|
|
|
189
189
|
|
|
190
190
|
## Local Models
|
|
191
191
|
|
|
192
|
-
|
|
192
|
+
21 models across 7 families. Sorted by quality — best models first.
|
|
193
193
|
|
|
194
194
|
### Top picks
|
|
195
195
|
|
|
@@ -240,7 +240,7 @@ Models from [Handy](https://github.com/cjpais/handy) (`~/Library/Application Sup
|
|
|
240
240
|
| Feature | Description |
|
|
241
241
|
| -------------------------------- | ---------------------------------------------------------------------------------------- |
|
|
242
242
|
| **Dual backend** | Deepgram (cloud, live streaming) or local models (offline, batch) — switch in settings |
|
|
243
|
-
| **
|
|
243
|
+
| **21 local models** | Parakeet, Whisper, Moonshine, SenseVoice, GigaAM, Paraformer, Qwen3 — with accuracy/speed ratings |
|
|
244
244
|
| **Unified settings panel** | One overlay panel for all configuration — `/voice-settings` |
|
|
245
245
|
| **Device-aware recommendations** | Scores models against your hardware. Only best-in-class models get [recommended]. |
|
|
246
246
|
| **Enterprise download pipeline** | Pre-checks (disk, network, permissions), live progress with speed/ETA, post-verification |
|
|
@@ -264,7 +264,7 @@ extensions/voice.ts Main extension — state machine, recording,
|
|
|
264
264
|
extensions/voice/config.ts Config loading, saving, migration
|
|
265
265
|
extensions/voice/onboarding.ts First-run wizard, language picker
|
|
266
266
|
extensions/voice/deepgram.ts Deepgram URL builder, API key resolver
|
|
267
|
-
extensions/voice/local.ts Model catalog (
|
|
267
|
+
extensions/voice/local.ts Model catalog (21 models), in-process transcription
|
|
268
268
|
extensions/voice/device.ts Device profiling — RAM, GPU, CPU, container detection
|
|
269
269
|
extensions/voice/model-download.ts Download manager — resume, progress, verification, Handy import
|
|
270
270
|
extensions/voice/sherpa-engine.ts sherpa-onnx bindings — recognizer lifecycle, inference
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capture-tool preference.
|
|
3
|
+
*
|
|
4
|
+
* SoX' `rec` is the best default for local capture, but it is known to stall on
|
|
5
|
+
* network PulseAudio servers: over an SSH audio tunnel (`PULSE_SERVER` pointing at
|
|
6
|
+
* a forwarded TCP port, e.g. `tcp:127.0.0.1:4713`) the same `rec` invocation
|
|
7
|
+
* returned zero bytes in 40% of runs on this stack, while ffmpeg was reliable in
|
|
8
|
+
* every run. So when a remote Pulse server is configured, probe ffmpeg first and
|
|
9
|
+
* keep SoX as the fallback; local capture keeps SoX first.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export type AudioToolName = "ffmpeg" | "sox" | "arecord";
|
|
13
|
+
|
|
14
|
+
export function audioToolOrder(env: { PULSE_SERVER?: string | undefined } = process.env): AudioToolName[] {
|
|
15
|
+
return env.PULSE_SERVER ? ["ffmpeg", "sox", "arecord"] : ["sox", "ffmpeg", "arecord"];
|
|
16
|
+
}
|
package/extensions/voice.ts
CHANGED
|
@@ -100,6 +100,7 @@ import {
|
|
|
100
100
|
} from "./voice/local";
|
|
101
101
|
import { shouldArmReleaseDetectOnRepeat, decideRecordingStartTimer } from "./voice/hold-to-talk";
|
|
102
102
|
import { GapTimer, type TimerPort } from "./voice/release-controller";
|
|
103
|
+
import { audioToolOrder, type AudioToolName } from "./voice/audio-tool";
|
|
103
104
|
|
|
104
105
|
/** Adapter for the real event loop — lets GapTimer run under the real setTimeout. */
|
|
105
106
|
const realTimerPort: TimerPort = {
|
|
@@ -245,87 +246,95 @@ interface AudioCaptureTool {
|
|
|
245
246
|
args: string[];
|
|
246
247
|
}
|
|
247
248
|
|
|
248
|
-
// Try available audio capture tools in order
|
|
249
|
+
// Try available audio capture tools in the order given by audioToolOrder()
|
|
249
250
|
let _cachedAudioTool: AudioCaptureTool | null | undefined;
|
|
250
|
-
function detectAudioCaptureTool(): AudioCaptureTool | null {
|
|
251
|
-
if (_cachedAudioTool !== undefined) return _cachedAudioTool;
|
|
252
251
|
|
|
253
|
-
|
|
254
|
-
if (commandExists("rec"))
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
}
|
|
252
|
+
function probeSox(): AudioCaptureTool | null {
|
|
253
|
+
if (!commandExists("rec")) return null;
|
|
254
|
+
return {
|
|
255
|
+
name: "sox",
|
|
256
|
+
cmd: "rec",
|
|
257
|
+
args: [
|
|
258
|
+
"-q",
|
|
259
|
+
"--buffer",
|
|
260
|
+
"4096",
|
|
261
|
+
"-c",
|
|
262
|
+
String(CHANNELS),
|
|
263
|
+
"-b",
|
|
264
|
+
"16",
|
|
265
|
+
"-e",
|
|
266
|
+
"signed-integer",
|
|
267
|
+
"-t",
|
|
268
|
+
"raw",
|
|
269
|
+
"-",
|
|
270
|
+
"rate",
|
|
271
|
+
String(SAMPLE_RATE),
|
|
272
|
+
],
|
|
273
|
+
};
|
|
274
|
+
}
|
|
277
275
|
|
|
278
|
-
|
|
279
|
-
if (commandExists("ffmpeg"))
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
} else {
|
|
296
|
-
inputArgs = ["-f", "pulse", "-i", "default"]; // fallback for other platforms
|
|
297
|
-
}
|
|
298
|
-
_cachedAudioTool = {
|
|
299
|
-
name: "ffmpeg",
|
|
300
|
-
cmd: "ffmpeg",
|
|
301
|
-
args: [
|
|
302
|
-
...inputArgs,
|
|
303
|
-
"-ac",
|
|
304
|
-
String(CHANNELS),
|
|
305
|
-
"-ar",
|
|
306
|
-
String(SAMPLE_RATE),
|
|
307
|
-
"-sample_fmt",
|
|
308
|
-
"s16",
|
|
309
|
-
"-f",
|
|
310
|
-
"s16le",
|
|
311
|
-
"-loglevel",
|
|
312
|
-
"error",
|
|
313
|
-
"pipe:1",
|
|
314
|
-
],
|
|
315
|
-
};
|
|
316
|
-
return _cachedAudioTool;
|
|
276
|
+
function probeFfmpeg(): AudioCaptureTool | null {
|
|
277
|
+
if (!commandExists("ffmpeg")) return null;
|
|
278
|
+
const isLinux = process.platform === "linux";
|
|
279
|
+
const isMac = process.platform === "darwin";
|
|
280
|
+
const isWin = process.platform === "win32";
|
|
281
|
+
// Input device varies by platform
|
|
282
|
+
let inputArgs: string[];
|
|
283
|
+
if (isMac) {
|
|
284
|
+
inputArgs = ["-f", "avfoundation", "-i", ":default"];
|
|
285
|
+
} else if (isLinux) {
|
|
286
|
+
inputArgs = ["-f", "pulse", "-i", "default"];
|
|
287
|
+
} else if (isWin) {
|
|
288
|
+
// DirectShow has no "default" alias — enumerate devices and pick the first audio device
|
|
289
|
+
const dshowDevice = detectWindowsAudioDevice();
|
|
290
|
+
inputArgs = dshowDevice ? ["-f", "dshow", "-i", `audio=${dshowDevice}`] : ["-f", "dshow", "-i", "audio=Microphone"]; // last-resort guess
|
|
291
|
+
} else {
|
|
292
|
+
inputArgs = ["-f", "pulse", "-i", "default"]; // fallback for other platforms
|
|
317
293
|
}
|
|
294
|
+
return {
|
|
295
|
+
name: "ffmpeg",
|
|
296
|
+
cmd: "ffmpeg",
|
|
297
|
+
args: [
|
|
298
|
+
...inputArgs,
|
|
299
|
+
"-ac",
|
|
300
|
+
String(CHANNELS),
|
|
301
|
+
"-ar",
|
|
302
|
+
String(SAMPLE_RATE),
|
|
303
|
+
"-sample_fmt",
|
|
304
|
+
"s16",
|
|
305
|
+
"-f",
|
|
306
|
+
"s16le",
|
|
307
|
+
"-loglevel",
|
|
308
|
+
"error",
|
|
309
|
+
"pipe:1",
|
|
310
|
+
],
|
|
311
|
+
};
|
|
312
|
+
}
|
|
318
313
|
|
|
319
|
-
|
|
320
|
-
if (process.platform
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
}
|
|
314
|
+
function probeArecord(): AudioCaptureTool | null {
|
|
315
|
+
if (process.platform !== "linux" || !commandExists("arecord")) return null;
|
|
316
|
+
return {
|
|
317
|
+
name: "arecord",
|
|
318
|
+
cmd: "arecord",
|
|
319
|
+
args: ["-q", "-f", "S16_LE", "-r", String(SAMPLE_RATE), "-c", String(CHANNELS), "-t", "raw"],
|
|
320
|
+
};
|
|
321
|
+
}
|
|
328
322
|
|
|
323
|
+
const audioProbes: Record<AudioToolName, () => AudioCaptureTool | null> = {
|
|
324
|
+
sox: probeSox,
|
|
325
|
+
ffmpeg: probeFfmpeg,
|
|
326
|
+
arecord: probeArecord,
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
function detectAudioCaptureTool(): AudioCaptureTool | null {
|
|
330
|
+
if (_cachedAudioTool !== undefined) return _cachedAudioTool;
|
|
331
|
+
for (const name of audioToolOrder()) {
|
|
332
|
+
const tool = audioProbes[name]();
|
|
333
|
+
if (tool) {
|
|
334
|
+
_cachedAudioTool = tool;
|
|
335
|
+
return tool;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
329
338
|
_cachedAudioTool = null;
|
|
330
339
|
return null;
|
|
331
340
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-voicekit",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Voice in + voice out for Pi CLI — hold-to-talk STT (Deepgram or
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "Voice in + voice out for Pi CLI — hold-to-talk STT (Deepgram streaming or 21 offline models) plus TTS (Kitten Nano, Piper, Kokoro, or Deepgram Aura)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"pi-package",
|
|
@@ -46,6 +46,8 @@
|
|
|
46
46
|
"typecheck": "bunx tsc -p tsconfig.json",
|
|
47
47
|
"test": "bun test",
|
|
48
48
|
"check": "bun run typecheck && bun run test",
|
|
49
|
+
"format": "bunx prettier@3.3.3 --write \"**/*.{ts,json,yml,yaml}\"",
|
|
50
|
+
"format:check": "bunx prettier@3.3.3 --check \"**/*.{ts,json,yml,yaml}\"",
|
|
49
51
|
"release:dry": "bun run check && bun publish --dry-run",
|
|
50
52
|
"release": "bun run check && bun publish --access public"
|
|
51
53
|
},
|