speechflow 1.6.5 → 1.6.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/CHANGELOG.md +19 -0
- package/README.md +23 -0
- package/etc/stx.conf +5 -0
- package/package.json +4 -4
- package/speechflow-cli/dst/speechflow-main-cli.js +2 -2
- package/speechflow-cli/dst/speechflow-main-cli.js.map +1 -1
- package/speechflow-cli/dst/speechflow-main-graph.js +4 -3
- package/speechflow-cli/dst/speechflow-main-graph.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.js +12 -11
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-expander.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-expander.js +12 -11
- package/speechflow-cli/dst/speechflow-node-a2a-expander.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js +2 -8
- package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-filler.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-filler.js +18 -16
- package/speechflow-cli/dst/speechflow-node-a2a-filler.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gain.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gain.js +8 -8
- package/speechflow-cli/dst/speechflow-node-a2a-gain.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gender.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gender.js +38 -34
- package/speechflow-cli/dst/speechflow-node-a2a-gender.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-meter.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-meter.js +11 -11
- package/speechflow-cli/dst/speechflow-node-a2a-meter.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-mute.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-mute.js +44 -10
- package/speechflow-cli/dst/speechflow-node-a2a-mute.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-pitch.d.ts +13 -0
- package/speechflow-cli/dst/speechflow-node-a2a-pitch.js +213 -0
- package/speechflow-cli/dst/speechflow-node-a2a-pitch.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js +12 -11
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-speex.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-speex.js +13 -12
- package/speechflow-cli/dst/speechflow-node-a2a-speex.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-vad.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-vad.js +24 -23
- package/speechflow-cli/dst/speechflow-node-a2a-vad.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-wav.js +35 -7
- package/speechflow-cli/dst/speechflow-node-a2a-wav.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-amazon.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-amazon.js +16 -16
- package/speechflow-cli/dst/speechflow-node-a2t-amazon.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js +16 -16
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-openai.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-openai.js +15 -15
- package/speechflow-cli/dst/speechflow-node-a2t-openai.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-amazon.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-amazon.js +9 -9
- package/speechflow-cli/dst/speechflow-node-t2a-amazon.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js +13 -12
- package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js +4 -4
- package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-amazon.js +3 -3
- package/speechflow-cli/dst/speechflow-node-t2t-amazon.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-deepl.js +2 -2
- package/speechflow-cli/dst/speechflow-node-t2t-deepl.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-format.js +36 -2
- package/speechflow-cli/dst/speechflow-node-t2t-format.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-google.js +2 -2
- package/speechflow-cli/dst/speechflow-node-t2t-google.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-modify.js +5 -5
- package/speechflow-cli/dst/speechflow-node-t2t-modify.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-ollama.js +2 -2
- package/speechflow-cli/dst/speechflow-node-t2t-ollama.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-openai.js +2 -2
- package/speechflow-cli/dst/speechflow-node-t2t-openai.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-sentence.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-sentence.js +13 -13
- package/speechflow-cli/dst/speechflow-node-t2t-sentence.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js +2 -2
- package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-transformers.js +2 -2
- package/speechflow-cli/dst/speechflow-node-t2t-transformers.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-x2x-filter.js +2 -2
- package/speechflow-cli/dst/speechflow-node-x2x-filter.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-x2x-trace.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-x2x-trace.js +42 -8
- package/speechflow-cli/dst/speechflow-node-x2x-trace.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-device.js +3 -2
- package/speechflow-cli/dst/speechflow-node-xio-device.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-file.js +19 -18
- package/speechflow-cli/dst/speechflow-node-xio-file.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-mqtt.js +13 -13
- package/speechflow-cli/dst/speechflow-node-xio-mqtt.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-websocket.js +8 -8
- package/speechflow-cli/dst/speechflow-node-xio-websocket.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node.js +7 -7
- package/speechflow-cli/dst/speechflow-node.js.map +1 -1
- package/speechflow-cli/dst/speechflow-util-audio.js +2 -2
- package/speechflow-cli/dst/speechflow-util-audio.js.map +1 -1
- package/speechflow-cli/dst/speechflow-util-stream.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-util-stream.js +22 -2
- package/speechflow-cli/dst/speechflow-util-stream.js.map +1 -1
- package/speechflow-cli/etc/oxlint.jsonc +2 -1
- package/speechflow-cli/etc/tsconfig.json +1 -0
- package/speechflow-cli/package.json +21 -21
- package/speechflow-cli/src/speechflow-main-cli.ts +2 -2
- package/speechflow-cli/src/speechflow-main-graph.ts +4 -3
- package/speechflow-cli/src/speechflow-node-a2a-compressor.ts +13 -12
- package/speechflow-cli/src/speechflow-node-a2a-expander.ts +13 -12
- package/speechflow-cli/src/speechflow-node-a2a-ffmpeg.ts +2 -8
- package/speechflow-cli/src/speechflow-node-a2a-filler.ts +19 -17
- package/speechflow-cli/src/speechflow-node-a2a-gain.ts +8 -8
- package/speechflow-cli/src/speechflow-node-a2a-gender.ts +42 -36
- package/speechflow-cli/src/speechflow-node-a2a-meter.ts +11 -11
- package/speechflow-cli/src/speechflow-node-a2a-mute.ts +11 -10
- package/speechflow-cli/src/speechflow-node-a2a-pitch.ts +221 -0
- package/speechflow-cli/src/speechflow-node-a2a-rnnoise.ts +13 -12
- package/speechflow-cli/src/speechflow-node-a2a-speex.ts +14 -13
- package/speechflow-cli/src/speechflow-node-a2a-vad.ts +24 -23
- package/speechflow-cli/src/speechflow-node-a2a-wav.ts +2 -7
- package/speechflow-cli/src/speechflow-node-a2t-amazon.ts +16 -16
- package/speechflow-cli/src/speechflow-node-a2t-deepgram.ts +16 -16
- package/speechflow-cli/src/speechflow-node-a2t-openai.ts +15 -15
- package/speechflow-cli/src/speechflow-node-t2a-amazon.ts +9 -9
- package/speechflow-cli/src/speechflow-node-t2a-elevenlabs.ts +13 -12
- package/speechflow-cli/src/speechflow-node-t2a-kokoro.ts +4 -4
- package/speechflow-cli/src/speechflow-node-t2t-amazon.ts +3 -3
- package/speechflow-cli/src/speechflow-node-t2t-deepl.ts +2 -2
- package/speechflow-cli/src/speechflow-node-t2t-format.ts +3 -2
- package/speechflow-cli/src/speechflow-node-t2t-google.ts +2 -2
- package/speechflow-cli/src/speechflow-node-t2t-modify.ts +6 -6
- package/speechflow-cli/src/speechflow-node-t2t-ollama.ts +2 -2
- package/speechflow-cli/src/speechflow-node-t2t-openai.ts +2 -2
- package/speechflow-cli/src/speechflow-node-t2t-sentence.ts +13 -13
- package/speechflow-cli/src/speechflow-node-t2t-subtitle.ts +2 -2
- package/speechflow-cli/src/speechflow-node-t2t-transformers.ts +2 -2
- package/speechflow-cli/src/speechflow-node-x2x-filter.ts +2 -2
- package/speechflow-cli/src/speechflow-node-x2x-trace.ts +10 -9
- package/speechflow-cli/src/speechflow-node-xio-device.ts +4 -3
- package/speechflow-cli/src/speechflow-node-xio-file.ts +20 -19
- package/speechflow-cli/src/speechflow-node-xio-mqtt.ts +14 -14
- package/speechflow-cli/src/speechflow-node-xio-websocket.ts +10 -10
- package/speechflow-cli/src/speechflow-node.ts +7 -7
- package/speechflow-cli/src/speechflow-util-audio.ts +2 -2
- package/speechflow-cli/src/speechflow-util-stream.ts +30 -5
- package/speechflow-ui-db/dst/app-font-fa-brands-400.woff2 +0 -0
- package/speechflow-ui-db/dst/app-font-fa-regular-400.woff2 +0 -0
- package/speechflow-ui-db/dst/app-font-fa-solid-900.woff2 +0 -0
- package/speechflow-ui-db/dst/app-font-fa-v4compatibility.woff2 +0 -0
- package/speechflow-ui-db/dst/index.css +1 -1
- package/speechflow-ui-db/dst/index.js +28 -25
- package/speechflow-ui-db/package.json +14 -14
- package/speechflow-ui-st/dst/app-font-fa-brands-400.woff2 +0 -0
- package/speechflow-ui-st/dst/app-font-fa-regular-400.woff2 +0 -0
- package/speechflow-ui-st/dst/app-font-fa-solid-900.woff2 +0 -0
- package/speechflow-ui-st/dst/app-font-fa-v4compatibility.woff2 +0 -0
- package/speechflow-ui-st/dst/index.css +1 -1
- package/speechflow-ui-st/dst/index.js +137 -51
- package/speechflow-ui-st/package.json +15 -15
|
@@ -19,7 +19,7 @@ export default class SpeechFlowNodeA2ARNNoise extends SpeechFlowNode {
|
|
|
19
19
|
public static name = "a2a-rnnoise"
|
|
20
20
|
|
|
21
21
|
/* internal state */
|
|
22
|
-
private
|
|
22
|
+
private closing = false
|
|
23
23
|
private sampleSize = 480 /* = 10ms at 48KHz, as required by RNNoise! */
|
|
24
24
|
private worker: Worker | null = null
|
|
25
25
|
|
|
@@ -38,7 +38,7 @@ export default class SpeechFlowNodeA2ARNNoise extends SpeechFlowNode {
|
|
|
38
38
|
/* open node */
|
|
39
39
|
async open () {
|
|
40
40
|
/* clear destruction flag */
|
|
41
|
-
this.
|
|
41
|
+
this.closing = false
|
|
42
42
|
|
|
43
43
|
/* initialize worker */
|
|
44
44
|
this.worker = new Worker(resolve(__dirname, "speechflow-node-a2a-rnnoise-wt.js"))
|
|
@@ -89,7 +89,7 @@ export default class SpeechFlowNodeA2ARNNoise extends SpeechFlowNode {
|
|
|
89
89
|
/* send message to worker */
|
|
90
90
|
let seq = 0
|
|
91
91
|
const workerProcessSegment = async (segment: Int16Array<ArrayBuffer>) => {
|
|
92
|
-
if (this.
|
|
92
|
+
if (this.closing)
|
|
93
93
|
return segment
|
|
94
94
|
const id = `${seq++}`
|
|
95
95
|
return new Promise<Int16Array<ArrayBuffer>>((resolve) => {
|
|
@@ -105,7 +105,7 @@ export default class SpeechFlowNodeA2ARNNoise extends SpeechFlowNode {
|
|
|
105
105
|
writableObjectMode: true,
|
|
106
106
|
decodeStrings: false,
|
|
107
107
|
transform (chunk: SpeechFlowChunk & { payload: Buffer }, encoding, callback) {
|
|
108
|
-
if (self.
|
|
108
|
+
if (self.closing) {
|
|
109
109
|
callback(new Error("stream already destroyed"))
|
|
110
110
|
return
|
|
111
111
|
}
|
|
@@ -128,14 +128,15 @@ export default class SpeechFlowNodeA2ARNNoise extends SpeechFlowNode {
|
|
|
128
128
|
/* forward updated chunk */
|
|
129
129
|
this.push(chunk)
|
|
130
130
|
callback()
|
|
131
|
-
}).catch((err:
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
}).catch((err: unknown) => {
|
|
132
|
+
const error = util.ensureError(err)
|
|
133
|
+
self.log("warning", `processing of chunk failed: ${error.message}`)
|
|
134
|
+
callback(error)
|
|
134
135
|
})
|
|
135
136
|
}
|
|
136
137
|
},
|
|
137
138
|
final (callback) {
|
|
138
|
-
if (self.
|
|
139
|
+
if (self.closing) {
|
|
139
140
|
callback()
|
|
140
141
|
return
|
|
141
142
|
}
|
|
@@ -147,8 +148,8 @@ export default class SpeechFlowNodeA2ARNNoise extends SpeechFlowNode {
|
|
|
147
148
|
|
|
148
149
|
/* close node */
|
|
149
150
|
async close () {
|
|
150
|
-
/* indicate
|
|
151
|
-
this.
|
|
151
|
+
/* indicate closing */
|
|
152
|
+
this.closing = true
|
|
152
153
|
|
|
153
154
|
/* shutdown worker */
|
|
154
155
|
if (this.worker !== null) {
|
|
@@ -156,9 +157,9 @@ export default class SpeechFlowNodeA2ARNNoise extends SpeechFlowNode {
|
|
|
156
157
|
this.worker = null
|
|
157
158
|
}
|
|
158
159
|
|
|
159
|
-
/*
|
|
160
|
+
/* shutdown stream */
|
|
160
161
|
if (this.stream !== null) {
|
|
161
|
-
this.stream
|
|
162
|
+
await util.destroyStream(this.stream)
|
|
162
163
|
this.stream = null
|
|
163
164
|
}
|
|
164
165
|
}
|
|
@@ -22,7 +22,7 @@ export default class SpeechFlowNodeA2ASpeex extends SpeechFlowNode {
|
|
|
22
22
|
public static name = "a2a-speex"
|
|
23
23
|
|
|
24
24
|
/* internal state */
|
|
25
|
-
private
|
|
25
|
+
private closing = false
|
|
26
26
|
private sampleSize = 480 /* = 10ms at 48KHz */
|
|
27
27
|
private speexProcessor: SpeexPreprocessor | null = null
|
|
28
28
|
|
|
@@ -43,7 +43,7 @@ export default class SpeechFlowNodeA2ASpeex extends SpeechFlowNode {
|
|
|
43
43
|
/* open node */
|
|
44
44
|
async open () {
|
|
45
45
|
/* clear destruction flag */
|
|
46
|
-
this.
|
|
46
|
+
this.closing = false
|
|
47
47
|
|
|
48
48
|
/* validate sample rate compatibility */
|
|
49
49
|
if (this.config.audioSampleRate !== 48000)
|
|
@@ -71,7 +71,7 @@ export default class SpeechFlowNodeA2ASpeex extends SpeechFlowNode {
|
|
|
71
71
|
writableObjectMode: true,
|
|
72
72
|
decodeStrings: false,
|
|
73
73
|
transform (chunk: SpeechFlowChunk & { payload: Buffer }, encoding, callback) {
|
|
74
|
-
if (self.
|
|
74
|
+
if (self.closing) {
|
|
75
75
|
callback(new Error("stream already destroyed"))
|
|
76
76
|
return
|
|
77
77
|
}
|
|
@@ -83,12 +83,12 @@ export default class SpeechFlowNodeA2ASpeex extends SpeechFlowNode {
|
|
|
83
83
|
|
|
84
84
|
/* process Int16Array in necessary fixed-size segments */
|
|
85
85
|
util.processInt16ArrayInSegments(payload, self.sampleSize, (segment) => {
|
|
86
|
-
if (self.
|
|
86
|
+
if (self.closing)
|
|
87
87
|
throw new Error("stream already destroyed")
|
|
88
88
|
self.speexProcessor?.processInt16(segment)
|
|
89
89
|
return Promise.resolve(segment)
|
|
90
90
|
}).then((payload: Int16Array<ArrayBuffer>) => {
|
|
91
|
-
if (self.
|
|
91
|
+
if (self.closing)
|
|
92
92
|
throw new Error("stream already destroyed")
|
|
93
93
|
|
|
94
94
|
/* convert Int16Array back into Buffer */
|
|
@@ -100,14 +100,15 @@ export default class SpeechFlowNodeA2ASpeex extends SpeechFlowNode {
|
|
|
100
100
|
/* forward updated chunk */
|
|
101
101
|
this.push(chunk)
|
|
102
102
|
callback()
|
|
103
|
-
}).catch((err:
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
}).catch((err: unknown) => {
|
|
104
|
+
const error = util.ensureError(err)
|
|
105
|
+
self.log("warning", `processing of chunk failed: ${error.message}`)
|
|
106
|
+
callback(error)
|
|
106
107
|
})
|
|
107
108
|
}
|
|
108
109
|
},
|
|
109
110
|
final (callback) {
|
|
110
|
-
if (self.
|
|
111
|
+
if (self.closing) {
|
|
111
112
|
callback()
|
|
112
113
|
return
|
|
113
114
|
}
|
|
@@ -119,8 +120,8 @@ export default class SpeechFlowNodeA2ASpeex extends SpeechFlowNode {
|
|
|
119
120
|
|
|
120
121
|
/* close node */
|
|
121
122
|
async close () {
|
|
122
|
-
/* indicate
|
|
123
|
-
this.
|
|
123
|
+
/* indicate closing */
|
|
124
|
+
this.closing = true
|
|
124
125
|
|
|
125
126
|
/* destroy processor */
|
|
126
127
|
if (this.speexProcessor !== null) {
|
|
@@ -128,9 +129,9 @@ export default class SpeechFlowNodeA2ASpeex extends SpeechFlowNode {
|
|
|
128
129
|
this.speexProcessor = null
|
|
129
130
|
}
|
|
130
131
|
|
|
131
|
-
/*
|
|
132
|
+
/* shutdown stream */
|
|
132
133
|
if (this.stream !== null) {
|
|
133
|
-
this.stream
|
|
134
|
+
await util.destroyStream(this.stream)
|
|
134
135
|
this.stream = null
|
|
135
136
|
}
|
|
136
137
|
}
|
|
@@ -40,7 +40,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
40
40
|
private queueRecv = this.queue.pointerUse("recv")
|
|
41
41
|
private queueVAD = this.queue.pointerUse("vad")
|
|
42
42
|
private queueSend = this.queue.pointerUse("send")
|
|
43
|
-
private
|
|
43
|
+
private closing = false
|
|
44
44
|
private tailTimer: ReturnType<typeof setTimeout> | null = null
|
|
45
45
|
private activeEventListeners = new Set<() => void>()
|
|
46
46
|
|
|
@@ -71,7 +71,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
71
71
|
throw new Error("VAD node currently supports PCM-S16LE audio only")
|
|
72
72
|
|
|
73
73
|
/* clear destruction flag */
|
|
74
|
-
this.
|
|
74
|
+
this.closing = false
|
|
75
75
|
|
|
76
76
|
/* internal processing constants */
|
|
77
77
|
const vadSampleRateTarget = 16000 /* internal target of VAD */
|
|
@@ -98,7 +98,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
98
98
|
redemptionFrames: this.params.redemptionFrames,
|
|
99
99
|
preSpeechPadFrames: this.params.preSpeechPadFrames,
|
|
100
100
|
onSpeechStart: () => {
|
|
101
|
-
if (this.
|
|
101
|
+
if (this.closing)
|
|
102
102
|
return
|
|
103
103
|
this.log("info", "VAD: speech start")
|
|
104
104
|
if (this.params.mode === "unplugged") {
|
|
@@ -107,7 +107,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
107
107
|
}
|
|
108
108
|
},
|
|
109
109
|
onSpeechEnd: (audio) => {
|
|
110
|
-
if (this.
|
|
110
|
+
if (this.closing)
|
|
111
111
|
return
|
|
112
112
|
const duration = util.audioArrayDuration(audio, vadSampleRateTarget)
|
|
113
113
|
this.log("info", `VAD: speech end (duration: ${duration.toFixed(2)}s)`)
|
|
@@ -115,7 +115,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
115
115
|
tail = true
|
|
116
116
|
clearTailTimer()
|
|
117
117
|
this.tailTimer = setTimeout(() => {
|
|
118
|
-
if (this.
|
|
118
|
+
if (this.closing || this.tailTimer === null)
|
|
119
119
|
return
|
|
120
120
|
tail = false
|
|
121
121
|
this.tailTimer = null
|
|
@@ -123,14 +123,14 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
123
123
|
}
|
|
124
124
|
},
|
|
125
125
|
onVADMisfire: () => {
|
|
126
|
-
if (this.
|
|
126
|
+
if (this.closing)
|
|
127
127
|
return
|
|
128
128
|
this.log("info", "VAD: speech end (segment too short)")
|
|
129
129
|
if (this.params.mode === "unplugged") {
|
|
130
130
|
tail = true
|
|
131
131
|
clearTailTimer()
|
|
132
132
|
this.tailTimer = setTimeout(() => {
|
|
133
|
-
if (this.
|
|
133
|
+
if (this.closing || this.tailTimer === null)
|
|
134
134
|
return
|
|
135
135
|
tail = false
|
|
136
136
|
this.tailTimer = null
|
|
@@ -138,7 +138,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
138
138
|
}
|
|
139
139
|
},
|
|
140
140
|
onFrameProcessed: (audio) => {
|
|
141
|
-
if (this.
|
|
141
|
+
if (this.closing)
|
|
142
142
|
return
|
|
143
143
|
try {
|
|
144
144
|
/* annotate the current audio segment */
|
|
@@ -178,7 +178,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
178
178
|
|
|
179
179
|
/* receive audio chunk (writable side of stream) */
|
|
180
180
|
write (chunk: SpeechFlowChunk, encoding, callback) {
|
|
181
|
-
if (self.
|
|
181
|
+
if (self.closing) {
|
|
182
182
|
callback(new Error("stream already destroyed"))
|
|
183
183
|
return
|
|
184
184
|
}
|
|
@@ -217,7 +217,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
217
217
|
})
|
|
218
218
|
|
|
219
219
|
/* push segments through Voice Activity Detection (VAD) */
|
|
220
|
-
if (self.vad && !self.
|
|
220
|
+
if (self.vad && !self.closing) {
|
|
221
221
|
try {
|
|
222
222
|
for (const segment of segmentData)
|
|
223
223
|
self.vad.processAudio(segment.data)
|
|
@@ -230,14 +230,14 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
230
230
|
callback()
|
|
231
231
|
}
|
|
232
232
|
catch (error) {
|
|
233
|
-
callback(error
|
|
233
|
+
callback(util.ensureError(error, "VAD processing failed"))
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
236
|
},
|
|
237
237
|
|
|
238
238
|
/* receive no more audio chunks (writable side of stream) */
|
|
239
239
|
final (callback) {
|
|
240
|
-
if (self.
|
|
240
|
+
if (self.closing) {
|
|
241
241
|
callback()
|
|
242
242
|
return
|
|
243
243
|
}
|
|
@@ -249,14 +249,14 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
249
249
|
|
|
250
250
|
/* send audio chunk(s) (readable side of stream) */
|
|
251
251
|
read (_size) {
|
|
252
|
-
if (self.
|
|
252
|
+
if (self.closing) {
|
|
253
253
|
this.push(null)
|
|
254
254
|
return
|
|
255
255
|
}
|
|
256
256
|
|
|
257
257
|
/* try to perform read operation from scratch */
|
|
258
258
|
const tryToRead = () => {
|
|
259
|
-
if (self.
|
|
259
|
+
if (self.closing) {
|
|
260
260
|
this.push(null)
|
|
261
261
|
return
|
|
262
262
|
}
|
|
@@ -265,7 +265,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
265
265
|
const flushPendingChunks = () => {
|
|
266
266
|
let pushed = 0
|
|
267
267
|
while (true) {
|
|
268
|
-
if (self.
|
|
268
|
+
if (self.closing) {
|
|
269
269
|
this.push(null)
|
|
270
270
|
return
|
|
271
271
|
}
|
|
@@ -297,7 +297,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
297
297
|
mode we else would be never called again until
|
|
298
298
|
we at least once push a new chunk as the result */
|
|
299
299
|
setTimeout(() => {
|
|
300
|
-
if (self.
|
|
300
|
+
if (self.closing || self.queue === null)
|
|
301
301
|
return
|
|
302
302
|
tryToRead()
|
|
303
303
|
}, 0)
|
|
@@ -308,14 +308,15 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
308
308
|
|
|
309
309
|
/* await forthcoming audio chunks */
|
|
310
310
|
const awaitForthcomingChunks = () => {
|
|
311
|
-
|
|
311
|
+
self.activeEventListeners.delete(awaitForthcomingChunks)
|
|
312
|
+
if (self.closing)
|
|
312
313
|
return
|
|
313
314
|
const element = self.queueSend.peek()
|
|
314
315
|
if (element !== undefined
|
|
315
316
|
&& element.type === "audio-frame"
|
|
316
317
|
&& element.isSpeech !== undefined)
|
|
317
318
|
flushPendingChunks()
|
|
318
|
-
else if (!self.
|
|
319
|
+
else if (!self.closing && !self.activeEventListeners.has(awaitForthcomingChunks)) {
|
|
319
320
|
self.queue.once("write", awaitForthcomingChunks)
|
|
320
321
|
self.activeEventListeners.add(awaitForthcomingChunks)
|
|
321
322
|
}
|
|
@@ -328,7 +329,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
328
329
|
&& element.type === "audio-frame"
|
|
329
330
|
&& element.isSpeech !== undefined)
|
|
330
331
|
flushPendingChunks()
|
|
331
|
-
else if (!self.
|
|
332
|
+
else if (!self.closing && !self.activeEventListeners.has(awaitForthcomingChunks)) {
|
|
332
333
|
self.queue.once("write", awaitForthcomingChunks)
|
|
333
334
|
self.activeEventListeners.add(awaitForthcomingChunks)
|
|
334
335
|
}
|
|
@@ -340,8 +341,8 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
340
341
|
|
|
341
342
|
/* close node */
|
|
342
343
|
async close () {
|
|
343
|
-
/* indicate
|
|
344
|
-
this.
|
|
344
|
+
/* indicate closing */
|
|
345
|
+
this.closing = true
|
|
345
346
|
|
|
346
347
|
/* cleanup tail timer */
|
|
347
348
|
if (this.tailTimer !== null) {
|
|
@@ -355,9 +356,9 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
355
356
|
})
|
|
356
357
|
this.activeEventListeners.clear()
|
|
357
358
|
|
|
358
|
-
/*
|
|
359
|
+
/* shutdown stream */
|
|
359
360
|
if (this.stream !== null) {
|
|
360
|
-
this.stream
|
|
361
|
+
await util.destroyStream(this.stream)
|
|
361
362
|
this.stream = null
|
|
362
363
|
}
|
|
363
364
|
|
|
@@ -9,6 +9,7 @@ import Stream from "node:stream"
|
|
|
9
9
|
|
|
10
10
|
/* internal dependencies */
|
|
11
11
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
12
|
+
import * as util from "./speechflow-util"
|
|
12
13
|
|
|
13
14
|
/* write WAV header */
|
|
14
15
|
const writeWavHeader = (
|
|
@@ -190,13 +191,7 @@ export default class SpeechFlowNodeA2AWAV extends SpeechFlowNode {
|
|
|
190
191
|
async close () {
|
|
191
192
|
/* shutdown stream */
|
|
192
193
|
if (this.stream !== null) {
|
|
193
|
-
await
|
|
194
|
-
if (this.stream instanceof Stream.Duplex)
|
|
195
|
-
this.stream.end(() => { resolve() })
|
|
196
|
-
else
|
|
197
|
-
resolve()
|
|
198
|
-
})
|
|
199
|
-
this.stream.destroy()
|
|
194
|
+
await util.destroyStream(this.stream)
|
|
200
195
|
this.stream = null
|
|
201
196
|
}
|
|
202
197
|
}
|
|
@@ -70,7 +70,7 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
|
|
|
70
70
|
/* internal state */
|
|
71
71
|
private client: TranscribeStreamingClient | null = null
|
|
72
72
|
private clientStream: AsyncIterable<TranscriptResultStream> | null = null
|
|
73
|
-
private
|
|
73
|
+
private closing = false
|
|
74
74
|
private initTimeout: ReturnType<typeof setTimeout> | null = null
|
|
75
75
|
private connectionTimeout: ReturnType<typeof setTimeout> | null = null
|
|
76
76
|
private queue: util.SingleQueue<SpeechFlowChunk | null> | null = null
|
|
@@ -111,7 +111,7 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
|
|
|
111
111
|
throw new Error("Amazon Transcribe node currently supports PCM-S16LE audio only")
|
|
112
112
|
|
|
113
113
|
/* clear destruction flag */
|
|
114
|
-
this.
|
|
114
|
+
this.closing = false
|
|
115
115
|
|
|
116
116
|
/* create queue for results */
|
|
117
117
|
this.queue = new util.SingleQueue<SpeechFlowChunk | null>()
|
|
@@ -130,7 +130,7 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
|
|
|
130
130
|
if (this.client === null)
|
|
131
131
|
throw new Error("failed to establish Amazon Transcribe client")
|
|
132
132
|
|
|
133
|
-
/*
|
|
133
|
+
/* create an AudioStream for Amazon Transcribe */
|
|
134
134
|
const audioQueue = new AsyncQueue<Uint8Array>()
|
|
135
135
|
const audioStream = (async function *(q: AsyncQueue<Uint8Array>): AsyncIterable<AudioStream> {
|
|
136
136
|
for await (const chunk of q) {
|
|
@@ -140,7 +140,7 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
|
|
|
140
140
|
|
|
141
141
|
/* start streaming */
|
|
142
142
|
const ensureAudioStreamActive = async () => {
|
|
143
|
-
if (this.clientStream !== null || this.
|
|
143
|
+
if (this.clientStream !== null || this.closing)
|
|
144
144
|
return
|
|
145
145
|
const language: LanguageCode = this.params.language === "de" ? "de-DE" : "en-US"
|
|
146
146
|
const command = new StartStreamTranscriptionCommand({
|
|
@@ -172,7 +172,7 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
|
|
|
172
172
|
const tsStart = Duration.fromMillis((result.StartTime ?? 0) * 1000).plus(this.timeZeroOffset)
|
|
173
173
|
const tsEnd = Duration.fromMillis((result.EndTime ?? 0) * 1000).plus(this.timeZeroOffset)
|
|
174
174
|
const metas = metastore.fetch(tsStart, tsEnd)
|
|
175
|
-
const meta = metas.reduce((prev: Map<string, any>, curr: Map<string, any>) => {
|
|
175
|
+
const meta = metas.toReversed().reduce((prev: Map<string, any>, curr: Map<string, any>) => {
|
|
176
176
|
curr.forEach((val, key) => { prev.set(key, val) })
|
|
177
177
|
return prev
|
|
178
178
|
}, new Map<string, any>())
|
|
@@ -210,7 +210,7 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
|
|
|
210
210
|
decodeStrings: false,
|
|
211
211
|
highWaterMark: 1,
|
|
212
212
|
write (chunk: SpeechFlowChunk, encoding, callback) {
|
|
213
|
-
if (self.
|
|
213
|
+
if (self.closing || self.client === null) {
|
|
214
214
|
callback(new Error("stream already destroyed"))
|
|
215
215
|
return
|
|
216
216
|
}
|
|
@@ -223,7 +223,7 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
|
|
|
223
223
|
self.log("debug", `send data (${chunk.payload.byteLength} bytes)`)
|
|
224
224
|
if (chunk.meta.size > 0)
|
|
225
225
|
metastore.store(chunk.timestampStart, chunk.timestampEnd, chunk.meta)
|
|
226
|
-
audioQueue.push(new Uint8Array(chunk.payload)) /*
|
|
226
|
+
audioQueue.push(new Uint8Array(chunk.payload)) /* intentionally discard all time information */
|
|
227
227
|
ensureAudioStreamActive().catch((error: unknown) => {
|
|
228
228
|
self.log("error", `failed to start audio stream: ${util.ensureError(error).message}`)
|
|
229
229
|
})
|
|
@@ -232,12 +232,12 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
|
|
|
232
232
|
}
|
|
233
233
|
},
|
|
234
234
|
read (size) {
|
|
235
|
-
if (self.
|
|
235
|
+
if (self.closing || self.queue === null) {
|
|
236
236
|
this.push(null)
|
|
237
237
|
return
|
|
238
238
|
}
|
|
239
239
|
self.queue.read().then((chunk) => {
|
|
240
|
-
if (self.
|
|
240
|
+
if (self.closing || self.queue === null) {
|
|
241
241
|
this.push(null)
|
|
242
242
|
return
|
|
243
243
|
}
|
|
@@ -250,12 +250,12 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
|
|
|
250
250
|
this.push(chunk)
|
|
251
251
|
}
|
|
252
252
|
}).catch((error: unknown) => {
|
|
253
|
-
if (!self.
|
|
253
|
+
if (!self.closing && self.queue !== null)
|
|
254
254
|
self.log("error", `queue read error: ${util.ensureError(error).message}`)
|
|
255
255
|
})
|
|
256
256
|
},
|
|
257
257
|
final (callback) {
|
|
258
|
-
if (self.
|
|
258
|
+
if (self.closing || self.client === null) {
|
|
259
259
|
callback()
|
|
260
260
|
return
|
|
261
261
|
}
|
|
@@ -263,7 +263,7 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
|
|
|
263
263
|
() => self.client!.destroy(),
|
|
264
264
|
(error: Error) => self.log("warning", `error closing Amazon Transcribe connection: ${error}`)
|
|
265
265
|
)
|
|
266
|
-
audioQueue.push(null) /*
|
|
266
|
+
audioQueue.push(null) /* do not push null to stream, let Amazon Transcribe do it */
|
|
267
267
|
audioQueue.destroy()
|
|
268
268
|
callback()
|
|
269
269
|
}
|
|
@@ -272,8 +272,8 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
|
|
|
272
272
|
|
|
273
273
|
/* close node */
|
|
274
274
|
async close () {
|
|
275
|
-
/* indicate
|
|
276
|
-
this.
|
|
275
|
+
/* indicate closing first to stop all async operations */
|
|
276
|
+
this.closing = true
|
|
277
277
|
|
|
278
278
|
/* cleanup all timers */
|
|
279
279
|
if (this.initTimeout !== null) {
|
|
@@ -297,9 +297,9 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
|
|
|
297
297
|
this.client = null
|
|
298
298
|
}
|
|
299
299
|
|
|
300
|
-
/*
|
|
300
|
+
/* shutdown stream */
|
|
301
301
|
if (this.stream !== null) {
|
|
302
|
-
this.stream
|
|
302
|
+
await util.destroyStream(this.stream)
|
|
303
303
|
this.stream = null
|
|
304
304
|
}
|
|
305
305
|
}
|
|
@@ -22,7 +22,7 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
|
|
|
22
22
|
|
|
23
23
|
/* internal state */
|
|
24
24
|
private dg: Deepgram.LiveClient | null = null
|
|
25
|
-
private
|
|
25
|
+
private closing = false
|
|
26
26
|
private initTimeout: ReturnType<typeof setTimeout> | null = null
|
|
27
27
|
private connectionTimeout: ReturnType<typeof setTimeout> | null = null
|
|
28
28
|
private queue: util.SingleQueue<SpeechFlowChunk | null> | null = null
|
|
@@ -75,7 +75,7 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
|
|
|
75
75
|
throw new Error("Deepgram node currently supports PCM-S16LE audio only")
|
|
76
76
|
|
|
77
77
|
/* clear destruction flag */
|
|
78
|
-
this.
|
|
78
|
+
this.closing = false
|
|
79
79
|
|
|
80
80
|
/* create queue for results */
|
|
81
81
|
this.queue = new util.SingleQueue<SpeechFlowChunk | null>()
|
|
@@ -114,7 +114,7 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
|
|
|
114
114
|
|
|
115
115
|
/* hook onto Deepgram API events */
|
|
116
116
|
this.dg.on(Deepgram.LiveTranscriptionEvents.Transcript, async (data) => {
|
|
117
|
-
if (this.
|
|
117
|
+
if (this.closing || this.queue === null)
|
|
118
118
|
return
|
|
119
119
|
const text = (data.channel?.alternatives[0]?.transcript ?? "") as string
|
|
120
120
|
const words = (data.channel?.alternatives[0]?.words ?? []) as
|
|
@@ -130,7 +130,7 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
|
|
|
130
130
|
const start = Duration.fromMillis(data.start * 1000).plus(this.timeZeroOffset)
|
|
131
131
|
const end = start.plus({ seconds: data.duration })
|
|
132
132
|
const metas = metastore.fetch(start, end)
|
|
133
|
-
const meta = metas.reduce((prev: Map<string, any>, curr: Map<string, any>) => {
|
|
133
|
+
const meta = metas.toReversed().reduce((prev: Map<string, any>, curr: Map<string, any>) => {
|
|
134
134
|
curr.forEach((val, key) => { prev.set(key, val) })
|
|
135
135
|
return prev
|
|
136
136
|
}, new Map<string, any>())
|
|
@@ -156,12 +156,12 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
|
|
|
156
156
|
})
|
|
157
157
|
this.dg.on(Deepgram.LiveTranscriptionEvents.Close, () => {
|
|
158
158
|
this.log("info", "connection close")
|
|
159
|
-
if (!this.
|
|
159
|
+
if (!this.closing && this.queue !== null)
|
|
160
160
|
this.queue.write(null)
|
|
161
161
|
})
|
|
162
162
|
this.dg.on(Deepgram.LiveTranscriptionEvents.Error, (error: Error) => {
|
|
163
163
|
this.log("error", `error: ${error.message}`)
|
|
164
|
-
if (!this.
|
|
164
|
+
if (!this.closing && this.queue !== null)
|
|
165
165
|
this.queue.write(null)
|
|
166
166
|
this.emit("error")
|
|
167
167
|
})
|
|
@@ -193,7 +193,7 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
|
|
|
193
193
|
decodeStrings: false,
|
|
194
194
|
highWaterMark: 1,
|
|
195
195
|
write (chunk: SpeechFlowChunk, encoding, callback) {
|
|
196
|
-
if (self.
|
|
196
|
+
if (self.closing || self.dg === null) {
|
|
197
197
|
callback(new Error("stream already destroyed"))
|
|
198
198
|
return
|
|
199
199
|
}
|
|
@@ -210,7 +210,7 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
|
|
|
210
210
|
self.dg.send(chunk.payload.buffer) /* intentionally discard all time information */
|
|
211
211
|
}
|
|
212
212
|
catch (error) {
|
|
213
|
-
callback(error
|
|
213
|
+
callback(util.ensureError(error, "failed to send to Deepgram"))
|
|
214
214
|
return
|
|
215
215
|
}
|
|
216
216
|
}
|
|
@@ -218,12 +218,12 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
|
|
|
218
218
|
}
|
|
219
219
|
},
|
|
220
220
|
read (size) {
|
|
221
|
-
if (self.
|
|
221
|
+
if (self.closing || self.queue === null) {
|
|
222
222
|
this.push(null)
|
|
223
223
|
return
|
|
224
224
|
}
|
|
225
225
|
self.queue.read().then((chunk) => {
|
|
226
|
-
if (self.
|
|
226
|
+
if (self.closing || self.queue === null) {
|
|
227
227
|
this.push(null)
|
|
228
228
|
return
|
|
229
229
|
}
|
|
@@ -236,12 +236,12 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
|
|
|
236
236
|
this.push(chunk)
|
|
237
237
|
}
|
|
238
238
|
}).catch((error: unknown) => {
|
|
239
|
-
if (!self.
|
|
239
|
+
if (!self.closing && self.queue !== null)
|
|
240
240
|
self.log("error", `queue read error: ${util.ensureError(error).message}`)
|
|
241
241
|
})
|
|
242
242
|
},
|
|
243
243
|
final (callback) {
|
|
244
|
-
if (self.
|
|
244
|
+
if (self.closing || self.dg === null) {
|
|
245
245
|
callback()
|
|
246
246
|
return
|
|
247
247
|
}
|
|
@@ -259,8 +259,8 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
|
|
|
259
259
|
|
|
260
260
|
/* close node */
|
|
261
261
|
async close () {
|
|
262
|
-
/* indicate
|
|
263
|
-
this.
|
|
262
|
+
/* indicate closing first to stop all async operations */
|
|
263
|
+
this.closing = true
|
|
264
264
|
|
|
265
265
|
/* cleanup all timers */
|
|
266
266
|
if (this.initTimeout !== null) {
|
|
@@ -272,9 +272,9 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
|
|
|
272
272
|
this.connectionTimeout = null
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
-
/*
|
|
275
|
+
/* shutdown stream */
|
|
276
276
|
if (this.stream !== null) {
|
|
277
|
-
this.stream
|
|
277
|
+
await util.destroyStream(this.stream)
|
|
278
278
|
this.stream = null
|
|
279
279
|
}
|
|
280
280
|
|