opencode-interrupt-plugin 0.4.6 → 0.4.8
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/dist/audio/monitor.d.ts +6 -5
- package/dist/audio/monitor.js +48 -50
- package/dist/tts/index.js +9 -1
- package/package.json +1 -1
package/dist/audio/monitor.d.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { EventEmitter } from 'events';
|
|
2
2
|
export declare class AudioMonitor extends EventEmitter {
|
|
3
|
-
private
|
|
3
|
+
private proc;
|
|
4
4
|
private isRunning;
|
|
5
5
|
private threshold;
|
|
6
|
-
private
|
|
7
|
-
|
|
6
|
+
private windowSamples;
|
|
7
|
+
private sumSquares;
|
|
8
|
+
private totalSamples;
|
|
9
|
+
constructor(threshold?: number, windowMs?: number);
|
|
8
10
|
start(): void;
|
|
9
|
-
private
|
|
11
|
+
private startCapture;
|
|
10
12
|
stop(): void;
|
|
11
|
-
private handleRMS;
|
|
12
13
|
setThreshold(threshold: number): void;
|
|
13
14
|
isActive(): boolean;
|
|
14
15
|
}
|
package/dist/audio/monitor.js
CHANGED
|
@@ -1,72 +1,84 @@
|
|
|
1
1
|
import { spawn } from 'child_process';
|
|
2
2
|
import { EventEmitter } from 'events';
|
|
3
3
|
import { debug } from '../log.js';
|
|
4
|
+
const SAMPLE_RATE = 44100;
|
|
5
|
+
const CHANNELS = 1;
|
|
6
|
+
const BITS = 16;
|
|
4
7
|
export class AudioMonitor extends EventEmitter {
|
|
5
|
-
|
|
8
|
+
proc = null;
|
|
6
9
|
isRunning = false;
|
|
7
10
|
threshold;
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
windowSamples;
|
|
12
|
+
sumSquares = 0;
|
|
13
|
+
totalSamples = 0;
|
|
14
|
+
constructor(threshold = 0.02, windowMs = 100) {
|
|
10
15
|
super();
|
|
11
16
|
this.threshold = threshold;
|
|
12
|
-
this.
|
|
17
|
+
this.windowSamples = Math.floor(SAMPLE_RATE * windowMs / 1000);
|
|
13
18
|
}
|
|
14
19
|
start() {
|
|
15
20
|
if (this.isRunning)
|
|
16
21
|
return;
|
|
17
|
-
debug('[interrupt-monitor] Starting audio monitor');
|
|
22
|
+
debug('[interrupt-monitor] Starting audio monitor (continuous mode)');
|
|
18
23
|
this.isRunning = true;
|
|
19
24
|
this.emit('started');
|
|
20
|
-
this.
|
|
25
|
+
this.startCapture();
|
|
21
26
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
startCapture() {
|
|
28
|
+
this.sumSquares = 0;
|
|
29
|
+
this.totalSamples = 0;
|
|
30
|
+
this.proc = spawn('sox', [
|
|
26
31
|
'-q', '-d',
|
|
27
|
-
'-t', '
|
|
28
|
-
'
|
|
29
|
-
'
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
'-t', 'raw',
|
|
33
|
+
'-b', String(BITS),
|
|
34
|
+
'-e', 'signed',
|
|
35
|
+
'-r', String(SAMPLE_RATE),
|
|
36
|
+
'-c', String(CHANNELS),
|
|
37
|
+
'-',
|
|
38
|
+
]);
|
|
39
|
+
this.proc.stdout?.on('data', (buf) => {
|
|
34
40
|
if (!this.isRunning)
|
|
35
41
|
return;
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
const BYTES_PER_SAMPLE = BITS / 8;
|
|
43
|
+
for (let i = 0; i + BYTES_PER_SAMPLE <= buf.length; i += BYTES_PER_SAMPLE) {
|
|
44
|
+
const sample = buf.readInt16LE(i);
|
|
45
|
+
this.sumSquares += sample * sample;
|
|
46
|
+
this.totalSamples++;
|
|
38
47
|
}
|
|
39
|
-
|
|
40
|
-
|
|
48
|
+
if (this.totalSamples >= this.windowSamples) {
|
|
49
|
+
const rms = Math.sqrt(this.sumSquares / this.totalSamples) / 32768;
|
|
41
50
|
debug(`[interrupt-monitor] RMS: ${rms.toFixed(6)} (threshold: ${this.threshold})`);
|
|
42
|
-
this.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
51
|
+
if (rms > this.threshold) {
|
|
52
|
+
this.emit('voice-detected', rms);
|
|
53
|
+
}
|
|
54
|
+
this.sumSquares = 0;
|
|
55
|
+
this.totalSamples = 0;
|
|
46
56
|
}
|
|
47
|
-
this.pollTimer = setTimeout(() => this.poll(), this.pollMs);
|
|
48
57
|
});
|
|
49
|
-
proc.on('error', (err) => {
|
|
58
|
+
this.proc.on('error', (err) => {
|
|
50
59
|
if (!this.isRunning)
|
|
51
60
|
return;
|
|
52
|
-
debug(`[interrupt-monitor] sox
|
|
61
|
+
debug(`[interrupt-monitor] sox error: ${err.message}`);
|
|
53
62
|
this.emit('error', new Error('sox not found. Install it: brew install sox (macOS) or apt install sox (Linux)'));
|
|
54
63
|
this.isRunning = false;
|
|
55
64
|
});
|
|
65
|
+
this.proc.on('close', (code) => {
|
|
66
|
+
if (!this.isRunning)
|
|
67
|
+
return;
|
|
68
|
+
debug(`[interrupt-monitor] sox exited (code ${code}), restarting...`);
|
|
69
|
+
if (this.isRunning) {
|
|
70
|
+
this.startCapture();
|
|
71
|
+
}
|
|
72
|
+
});
|
|
56
73
|
}
|
|
57
74
|
stop() {
|
|
58
75
|
this.isRunning = false;
|
|
59
|
-
if (this.
|
|
60
|
-
|
|
61
|
-
this.
|
|
76
|
+
if (this.proc) {
|
|
77
|
+
this.proc.kill();
|
|
78
|
+
this.proc = null;
|
|
62
79
|
}
|
|
63
80
|
this.emit('stopped');
|
|
64
81
|
}
|
|
65
|
-
handleRMS(rms) {
|
|
66
|
-
if (rms > this.threshold) {
|
|
67
|
-
this.emit('voice-detected', rms);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
82
|
setThreshold(threshold) {
|
|
71
83
|
this.threshold = threshold;
|
|
72
84
|
}
|
|
@@ -74,17 +86,3 @@ export class AudioMonitor extends EventEmitter {
|
|
|
74
86
|
return this.isRunning;
|
|
75
87
|
}
|
|
76
88
|
}
|
|
77
|
-
function parseRMSLine(line) {
|
|
78
|
-
const match = line.match(/RMS\s+amplitude:\s+([\d.]+)/);
|
|
79
|
-
if (match)
|
|
80
|
-
return parseFloat(match[1]);
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
function parseRMSFromStat(output) {
|
|
84
|
-
for (const line of output.split('\n')) {
|
|
85
|
-
const rms = parseRMSLine(line);
|
|
86
|
-
if (rms !== null)
|
|
87
|
-
return rms;
|
|
88
|
-
}
|
|
89
|
-
return null;
|
|
90
|
-
}
|
package/dist/tts/index.js
CHANGED
|
@@ -129,6 +129,8 @@ export class TTSStreamer extends EventEmitter {
|
|
|
129
129
|
return this.engine.isPlaying || this.queue.length > 0 || this.processing || (Date.now() - this.lastActivity < 3000);
|
|
130
130
|
}
|
|
131
131
|
resetSession() {
|
|
132
|
+
this.stopped = false;
|
|
133
|
+
this.processing = false;
|
|
132
134
|
this.buffer.clear();
|
|
133
135
|
this.queue = [];
|
|
134
136
|
this.spokenText = "";
|
|
@@ -136,9 +138,15 @@ export class TTSStreamer extends EventEmitter {
|
|
|
136
138
|
}
|
|
137
139
|
async processQueue() {
|
|
138
140
|
this.processing = true;
|
|
141
|
+
debug(`[interrupt-tts] processQueue start, queue=${this.queue.length}, stopped=${this.stopped}`);
|
|
139
142
|
while (this.queue.length > 0 && !this.stopped) {
|
|
140
143
|
const sentence = this.queue.shift();
|
|
141
|
-
|
|
144
|
+
try {
|
|
145
|
+
await this.engine.speak(sentence);
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
debug(`[interrupt-tts] TTS error in processQueue: ${err}`);
|
|
149
|
+
}
|
|
142
150
|
if (!this.stopped) {
|
|
143
151
|
this.spokenText += sentence + " ";
|
|
144
152
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-interrupt-plugin",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.8",
|
|
4
4
|
"description": "Streaming TTS + voice interruption for OpenCode. Speaks responses as they arrive and detects when you talk over it.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|