opencode-interrupt-plugin 0.4.5 → 0.4.7
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/config.js +1 -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/config.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-interrupt-plugin",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
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",
|