speechflow 2.1.0 → 2.1.1
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 +10 -0
- package/README.md +14 -11
- package/package.json +6 -6
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js +11 -7
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-sentence.d.ts +3 -3
- package/speechflow-cli/dst/speechflow-node-t2t-sentence.js +117 -100
- package/speechflow-cli/dst/speechflow-node-t2t-sentence.js.map +1 -1
- package/speechflow-cli/dst/{speechflow-node-a2t-assemblyai.d.ts → speechflow-node-t2t-simulator.d.ts} +1 -6
- package/speechflow-cli/dst/speechflow-node-t2t-simulator.js +128 -0
- package/speechflow-cli/dst/speechflow-node-t2t-simulator.js.map +1 -0
- package/speechflow-cli/dst/speechflow-util-queue.d.ts +9 -3
- package/speechflow-cli/dst/speechflow-util-queue.js +39 -17
- package/speechflow-cli/dst/speechflow-util-queue.js.map +1 -1
- package/speechflow-cli/package.json +28 -25
- package/speechflow-cli/src/speechflow-node-a2t-deepgram.ts +17 -14
- package/speechflow-cli/src/speechflow-node-t2t-sentence.ts +128 -112
- package/speechflow-cli/src/speechflow-util-queue.ts +44 -19
- 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 +47 -46
- package/speechflow-ui-db/package.json +18 -16
- package/speechflow-ui-db/src/app.vue +2 -2
- 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 +65 -64
- package/speechflow-ui-st/package.json +19 -17
- package/speechflow-cli/dst/speechflow-node-a2t-assemblyai.js +0 -275
- package/speechflow-cli/dst/speechflow-node-a2t-assemblyai.js.map +0 -1
- /package/speechflow-cli/package.d/{sherpa-onnx+1.12.23.patch → sherpa-onnx+1.12.25.patch} +0 -0
|
@@ -18,8 +18,7 @@ import * as util from "./speechflow-util"
|
|
|
18
18
|
type TextQueueElement = {
|
|
19
19
|
type: "text-frame",
|
|
20
20
|
chunk: SpeechFlowChunk,
|
|
21
|
-
|
|
22
|
-
complete?: boolean
|
|
21
|
+
complete: boolean
|
|
23
22
|
} | {
|
|
24
23
|
type: "text-eof"
|
|
25
24
|
}
|
|
@@ -31,12 +30,11 @@ export default class SpeechFlowNodeT2TSentence extends SpeechFlowNode {
|
|
|
31
30
|
|
|
32
31
|
/* internal state */
|
|
33
32
|
private queue = new util.Queue<TextQueueElement>()
|
|
34
|
-
private queueRecv = this.queue.pointerUse("recv")
|
|
35
|
-
private queueSplit = this.queue.pointerUse("split")
|
|
36
33
|
private queueSend = this.queue.pointerUse("send")
|
|
37
|
-
private
|
|
34
|
+
private queueSplit = this.queue.pointerUse("split")
|
|
35
|
+
private queueRecv = this.queue.pointerUse("recv")
|
|
36
|
+
private closing = false
|
|
38
37
|
private workingOffTimer: ReturnType<typeof setTimeout> | null = null
|
|
39
|
-
private previewTimer: ReturnType<typeof setTimeout> | null = null
|
|
40
38
|
|
|
41
39
|
/* construct node */
|
|
42
40
|
constructor (id: string, cfg: { [ id: string ]: any }, opts: { [ id: string ]: any }, args: any[]) {
|
|
@@ -44,7 +42,8 @@ export default class SpeechFlowNodeT2TSentence extends SpeechFlowNode {
|
|
|
44
42
|
|
|
45
43
|
/* declare node configuration parameters */
|
|
46
44
|
this.configure({
|
|
47
|
-
timeout: { type: "number",
|
|
45
|
+
timeout: { type: "number", pos: 0, val: 3 * 1000 },
|
|
46
|
+
interim: { type: "boolean", pos: 1, val: false }
|
|
48
47
|
})
|
|
49
48
|
|
|
50
49
|
/* declare node input/output format */
|
|
@@ -52,6 +51,14 @@ export default class SpeechFlowNodeT2TSentence extends SpeechFlowNode {
|
|
|
52
51
|
this.output = "text"
|
|
53
52
|
}
|
|
54
53
|
|
|
54
|
+
/* concatenate two payloads with proper whitespacing */
|
|
55
|
+
private concatPayload (s1: string, s2: string) {
|
|
56
|
+
if (!(s1.match(/\s+$/) || s2.match(/^\s+/)))
|
|
57
|
+
return `${s1} ${s2}`
|
|
58
|
+
else
|
|
59
|
+
return `${s1}${s2}`
|
|
60
|
+
}
|
|
61
|
+
|
|
55
62
|
/* open node */
|
|
56
63
|
async open () {
|
|
57
64
|
/* clear destruction flag */
|
|
@@ -83,84 +90,101 @@ export default class SpeechFlowNodeT2TSentence extends SpeechFlowNode {
|
|
|
83
90
|
break
|
|
84
91
|
}
|
|
85
92
|
|
|
86
|
-
/*
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (m !== null) {
|
|
91
|
-
/* contains a sentence */
|
|
92
|
-
const [ , sentence, rest ] = m
|
|
93
|
-
if (rest !== "") {
|
|
94
|
-
/* contains more than a sentence */
|
|
95
|
-
const chunk2 = chunk.clone()
|
|
96
|
-
const duration = Duration.fromMillis(
|
|
97
|
-
chunk.timestampEnd.minus(chunk.timestampStart).toMillis() *
|
|
98
|
-
(sentence.length / payload.length))
|
|
99
|
-
chunk2.timestampStart = chunk.timestampStart.plus(duration)
|
|
100
|
-
chunk.timestampEnd = chunk2.timestampStart
|
|
101
|
-
chunk.payload = sentence
|
|
102
|
-
chunk2.payload = rest
|
|
103
|
-
element.complete = true
|
|
104
|
-
this.queueSplit.touch()
|
|
105
|
-
this.queueSplit.walk(+1)
|
|
106
|
-
this.queueSplit.insert({ type: "text-frame", chunk: chunk2 })
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
/* contains just the sentence */
|
|
110
|
-
element.complete = true
|
|
111
|
-
this.queueSplit.touch()
|
|
112
|
-
this.queueSplit.walk(+1)
|
|
113
|
-
}
|
|
93
|
+
/* skip elements already completed */
|
|
94
|
+
if (element.type === "text-frame" && element.chunk.kind === "final" && element.complete === true) {
|
|
95
|
+
this.queueSplit.walk(+1)
|
|
96
|
+
continue
|
|
114
97
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
98
|
+
|
|
99
|
+
/* perform sentence splitting on input chunk */
|
|
100
|
+
if (element.chunk.kind === "final") {
|
|
101
|
+
const chunk = element.chunk
|
|
102
|
+
const payload = chunk.payload as string
|
|
103
|
+
const m = payload.match(/^((?:.|\r?\n)+?[.;?!])(?:\s+((?:.|\r?\n)+)|\s*)$/)
|
|
104
|
+
if (m !== null) {
|
|
105
|
+
/* contains a sentence */
|
|
106
|
+
const [ , sentence, rest ] = m
|
|
107
|
+
if (rest !== undefined && rest !== "") {
|
|
108
|
+
/* contains more than a sentence */
|
|
109
|
+
const chunk2 = chunk.clone()
|
|
110
|
+
const duration = Duration.fromMillis(
|
|
111
|
+
chunk.timestampEnd.minus(chunk.timestampStart).toMillis() *
|
|
112
|
+
(sentence.length / payload.length))
|
|
113
|
+
chunk2.timestampStart = chunk.timestampStart.plus(duration)
|
|
114
|
+
chunk.timestampEnd = chunk2.timestampStart
|
|
115
|
+
chunk.payload = sentence
|
|
116
|
+
chunk2.payload = rest
|
|
126
117
|
element.complete = true
|
|
118
|
+
this.queue.silent(true)
|
|
127
119
|
this.queueSplit.touch()
|
|
120
|
+
this.queue.silent(false)
|
|
128
121
|
this.queueSplit.walk(+1)
|
|
129
|
-
|
|
122
|
+
this.queueSplit.insert({ type: "text-frame", chunk: chunk2, complete: false })
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
/* contains just the sentence */
|
|
126
|
+
element.complete = true
|
|
127
|
+
this.queue.silent(true)
|
|
128
|
+
this.queueSplit.silent(true)
|
|
129
|
+
const position = this.queueSplit.position()
|
|
130
|
+
this.queueSplit.walk(+1)
|
|
131
|
+
this.queue.silent(false)
|
|
132
|
+
this.queueSplit.silent(false)
|
|
133
|
+
this.queueSplit.touch(position)
|
|
130
134
|
}
|
|
131
|
-
|
|
132
|
-
/* merge into following chunk */
|
|
133
|
-
element2.chunk.timestampStart = element.chunk.timestampStart
|
|
134
|
-
element2.chunk.payload =
|
|
135
|
-
(element.chunk.payload as string) + " " +
|
|
136
|
-
(element2.chunk.payload as string)
|
|
137
|
-
|
|
138
|
-
/* reset preview state (merged content needs new preview) */
|
|
139
|
-
element2.preview = undefined
|
|
140
|
-
this.queueSplit.delete()
|
|
141
|
-
this.queueSplit.touch()
|
|
142
135
|
}
|
|
143
136
|
else {
|
|
144
|
-
/*
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
137
|
+
/* contains less than a sentence */
|
|
138
|
+
const position = this.queueSplit.position()
|
|
139
|
+
if (position < this.queueSplit.maxPosition() - 1) {
|
|
140
|
+
/* merge into following chunk */
|
|
141
|
+
const element2 = this.queueSplit.peek(position + 1)
|
|
142
|
+
if (element2 === undefined)
|
|
143
|
+
break
|
|
144
|
+
if (element2.type === "text-eof") {
|
|
145
|
+
/* no more chunks: output as final
|
|
146
|
+
(perhaps incomplete sentence at end of stream) */
|
|
147
|
+
element.complete = true
|
|
148
|
+
this.queueSplit.walk(+1)
|
|
149
|
+
this.queueSplit.touch(this.queueSplit.position() - 1)
|
|
150
|
+
break
|
|
151
|
+
}
|
|
152
|
+
if (element2.chunk.kind === "final") {
|
|
153
|
+
/* merge into following chunk */
|
|
154
|
+
element2.chunk.timestampStart = element.chunk.timestampStart
|
|
155
|
+
element2.chunk.payload = this.concatPayload(element.chunk.payload as string,
|
|
156
|
+
element2.chunk.payload as string)
|
|
157
|
+
|
|
158
|
+
/* remove current element and touch now current element */
|
|
159
|
+
this.queue.silent(true)
|
|
160
|
+
this.queueSplit.delete()
|
|
161
|
+
this.queue.silent(false)
|
|
162
|
+
this.queueSplit.touch()
|
|
163
|
+
}
|
|
164
|
+
else
|
|
165
|
+
break
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
/* no following chunk yet */
|
|
169
|
+
break
|
|
148
170
|
}
|
|
149
|
-
break
|
|
150
171
|
}
|
|
151
172
|
}
|
|
173
|
+
else
|
|
174
|
+
break
|
|
152
175
|
}
|
|
153
176
|
|
|
154
177
|
/* re-initiate working off round (if still not destroyed) */
|
|
155
|
-
workingOff = false
|
|
156
178
|
if (!this.closing) {
|
|
157
179
|
this.workingOffTimer = setTimeout(workOffQueue, 100)
|
|
158
180
|
this.queue.once("write", workOffQueue)
|
|
159
181
|
}
|
|
182
|
+
workingOff = false
|
|
160
183
|
}
|
|
161
184
|
this.queue.once("write", workOffQueue)
|
|
162
185
|
|
|
163
186
|
/* provide Duplex stream and internally attach to classifier */
|
|
187
|
+
let previewed = false
|
|
164
188
|
const self = this
|
|
165
189
|
this.stream = new Stream.Duplex({
|
|
166
190
|
writableObjectMode: true,
|
|
@@ -169,37 +193,38 @@ export default class SpeechFlowNodeT2TSentence extends SpeechFlowNode {
|
|
|
169
193
|
highWaterMark: 1,
|
|
170
194
|
|
|
171
195
|
/* receive text chunk (writable side of stream) */
|
|
172
|
-
write (chunk: SpeechFlowChunk, encoding, callback) {
|
|
196
|
+
write (chunk: SpeechFlowChunk, encoding: BufferEncoding, callback: (error?: Error | null) => void) {
|
|
173
197
|
if (self.closing)
|
|
174
198
|
callback(new Error("stream already destroyed"))
|
|
175
199
|
else if (Buffer.isBuffer(chunk.payload))
|
|
176
200
|
callback(new Error("expected text input as string chunks"))
|
|
177
201
|
else if (chunk.payload.length === 0)
|
|
178
202
|
callback()
|
|
179
|
-
else if (chunk.kind === "intermediate") {
|
|
180
|
-
/* intermediate chunks: pass through immediately (bypass queue) */
|
|
181
|
-
self.log("info", `received text (${chunk.kind}): ${JSON.stringify(chunk.payload)}`)
|
|
182
|
-
self.log("info", `send text (intermediate pass-through): ${JSON.stringify(chunk.payload)}`)
|
|
183
|
-
this.push(chunk)
|
|
184
|
-
callback()
|
|
185
|
-
}
|
|
186
203
|
else {
|
|
187
204
|
/* final chunks: queue for sentence splitting */
|
|
188
205
|
self.log("info", `received text (${chunk.kind}): ${JSON.stringify(chunk.payload)}`)
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
206
|
+
const recvPos = self.queueRecv.position()
|
|
207
|
+
if (recvPos > 0) {
|
|
208
|
+
const element = self.queueRecv.peek(recvPos - 1)
|
|
209
|
+
if (element) {
|
|
210
|
+
if (element.type === "text-eof") {
|
|
211
|
+
callback(new Error("received text input after end-of-stream"))
|
|
212
|
+
return
|
|
213
|
+
}
|
|
214
|
+
if (element.chunk.kind === "intermediate") {
|
|
215
|
+
self.queueRecv.walk(-1)
|
|
216
|
+
self.queueRecv.delete()
|
|
217
|
+
}
|
|
218
|
+
}
|
|
194
219
|
}
|
|
195
|
-
|
|
196
|
-
self.queueRecv.append({ type: "text-frame", chunk })
|
|
220
|
+
previewed = false
|
|
221
|
+
self.queueRecv.append({ type: "text-frame", chunk, complete: false })
|
|
197
222
|
callback()
|
|
198
223
|
}
|
|
199
224
|
},
|
|
200
225
|
|
|
201
226
|
/* receive no more text chunks (writable side of stream) */
|
|
202
|
-
final (callback) {
|
|
227
|
+
final (callback: (error?: Error | null) => void) {
|
|
203
228
|
if (self.closing) {
|
|
204
229
|
callback()
|
|
205
230
|
return
|
|
@@ -227,6 +252,7 @@ export default class SpeechFlowNodeT2TSentence extends SpeechFlowNode {
|
|
|
227
252
|
&& element.type === "text-frame"
|
|
228
253
|
&& element.complete === true) {
|
|
229
254
|
/* send all consecutive complete chunks */
|
|
255
|
+
let eofSeen = false
|
|
230
256
|
while (true) {
|
|
231
257
|
const nextElement = self.queueSend.peek()
|
|
232
258
|
if (nextElement === undefined)
|
|
@@ -234,49 +260,43 @@ export default class SpeechFlowNodeT2TSentence extends SpeechFlowNode {
|
|
|
234
260
|
else if (nextElement.type === "text-eof") {
|
|
235
261
|
this.push(null)
|
|
236
262
|
self.queueSend.walk(+1)
|
|
263
|
+
eofSeen = true
|
|
237
264
|
break
|
|
238
265
|
}
|
|
239
266
|
else if (nextElement.type === "text-frame"
|
|
240
267
|
&& nextElement.complete !== true)
|
|
241
268
|
break
|
|
242
|
-
self.log("info", `send text (${nextElement.chunk.kind}): ${JSON.stringify(nextElement.chunk.payload)}`)
|
|
269
|
+
self.log("info", `send text 1 (${nextElement.chunk.kind}): ${JSON.stringify(nextElement.chunk.payload)} pos=${self.queueSend.position()}`)
|
|
243
270
|
this.push(nextElement.chunk)
|
|
244
271
|
self.queueSend.walk(+1)
|
|
245
272
|
self.queue.trim()
|
|
246
273
|
}
|
|
274
|
+
|
|
275
|
+
/* wait for more data (unless end-of-stream was reached) */
|
|
276
|
+
if (!eofSeen && !self.closing)
|
|
277
|
+
self.queue.once("write", flushPendingChunks)
|
|
247
278
|
}
|
|
248
279
|
else if (element !== undefined
|
|
249
280
|
&& element.type === "text-frame"
|
|
250
|
-
&& element.
|
|
251
|
-
|
|
281
|
+
&& element.complete === false
|
|
282
|
+
&& !previewed
|
|
283
|
+
&& self.params.interim === true) {
|
|
284
|
+
/* merge together all still queued elements and
|
|
285
|
+
send this out as an intermediate chunk as preview */
|
|
252
286
|
const previewChunk = element.chunk.clone()
|
|
253
287
|
previewChunk.kind = "intermediate"
|
|
254
|
-
self.
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
self.previewTimer = setTimeout(() => {
|
|
263
|
-
self.previewTimer = null
|
|
264
|
-
if (self.closing)
|
|
265
|
-
return
|
|
266
|
-
|
|
267
|
-
/* promote preview to final chunk */
|
|
268
|
-
const el = self.queueSend.peek()
|
|
269
|
-
if (el !== undefined
|
|
270
|
-
&& el.type === "text-frame"
|
|
271
|
-
&& el.preview === "sent"
|
|
272
|
-
&& el.complete !== true) {
|
|
273
|
-
self.log("info", `timeout: promoting intermediate to final: ${JSON.stringify(el.chunk.payload)}`)
|
|
274
|
-
el.complete = true
|
|
275
|
-
self.queueSend.touch()
|
|
276
|
-
self.queue.emit("write")
|
|
277
|
-
}
|
|
278
|
-
}, timeout)
|
|
288
|
+
for (let pos = self.queueSend.position() + 1; pos < self.queueSend.maxPosition(); pos++) {
|
|
289
|
+
const element2 = self.queueSend.peek(pos)
|
|
290
|
+
if (!element2)
|
|
291
|
+
continue
|
|
292
|
+
if (element2.type === "text-eof")
|
|
293
|
+
break
|
|
294
|
+
previewChunk.payload = self.concatPayload(
|
|
295
|
+
previewChunk.payload as string, element2.chunk.payload as string)
|
|
279
296
|
}
|
|
297
|
+
this.push(previewChunk)
|
|
298
|
+
self.log("info", `send text 2 (intermediate): ${JSON.stringify(previewChunk.payload)}`)
|
|
299
|
+
previewed = true
|
|
280
300
|
|
|
281
301
|
/* wait for more data */
|
|
282
302
|
if (!self.closing)
|
|
@@ -300,10 +320,6 @@ export default class SpeechFlowNodeT2TSentence extends SpeechFlowNode {
|
|
|
300
320
|
clearTimeout(this.workingOffTimer)
|
|
301
321
|
this.workingOffTimer = null
|
|
302
322
|
}
|
|
303
|
-
if (this.previewTimer !== null) {
|
|
304
|
-
clearTimeout(this.previewTimer)
|
|
305
|
-
this.previewTimer = null
|
|
306
|
-
}
|
|
307
323
|
|
|
308
324
|
/* remove any pending event listeners */
|
|
309
325
|
this.queue.removeAllListeners("write")
|
|
@@ -97,6 +97,7 @@ export type QueueElement = { type: string }
|
|
|
97
97
|
export class QueuePointer<T extends QueueElement> extends EventEmitter {
|
|
98
98
|
/* internal state */
|
|
99
99
|
private index = 0
|
|
100
|
+
private silence = false
|
|
100
101
|
|
|
101
102
|
/* construction */
|
|
102
103
|
constructor (
|
|
@@ -107,6 +108,17 @@ export class QueuePointer<T extends QueueElement> extends EventEmitter {
|
|
|
107
108
|
this.setMaxListeners(100)
|
|
108
109
|
}
|
|
109
110
|
|
|
111
|
+
/* control silence operation */
|
|
112
|
+
silent (silence: boolean) {
|
|
113
|
+
this.silence = silence
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* notify about operation */
|
|
117
|
+
notify (event: string, info: any) {
|
|
118
|
+
if (!this.silence)
|
|
119
|
+
this.emit(event, info)
|
|
120
|
+
}
|
|
121
|
+
|
|
110
122
|
/* positioning operations */
|
|
111
123
|
maxPosition () {
|
|
112
124
|
return this.queue.elements.length
|
|
@@ -114,7 +126,7 @@ export class QueuePointer<T extends QueueElement> extends EventEmitter {
|
|
|
114
126
|
position (index?: number): number {
|
|
115
127
|
if (index !== undefined) {
|
|
116
128
|
this.index = Math.max(0, Math.min(index, this.queue.elements.length))
|
|
117
|
-
this.
|
|
129
|
+
this.notify("position", this.index)
|
|
118
130
|
}
|
|
119
131
|
return this.index
|
|
120
132
|
}
|
|
@@ -125,19 +137,19 @@ export class QueuePointer<T extends QueueElement> extends EventEmitter {
|
|
|
125
137
|
else if (num < 0)
|
|
126
138
|
this.index = Math.max(this.index + num, 0)
|
|
127
139
|
if (this.index !== indexOld)
|
|
128
|
-
this.
|
|
140
|
+
this.notify("position", { start: this.index })
|
|
129
141
|
}
|
|
130
142
|
walkForwardUntil (type: T["type"]) {
|
|
131
143
|
while (this.index < this.queue.elements.length
|
|
132
144
|
&& this.queue.elements[this.index].type !== type)
|
|
133
145
|
this.index++
|
|
134
|
-
this.
|
|
146
|
+
this.notify("position", { start: this.index })
|
|
135
147
|
}
|
|
136
148
|
walkBackwardUntil (type: T["type"]) {
|
|
137
149
|
while (this.index > 0
|
|
138
150
|
&& this.queue.elements[this.index].type !== type)
|
|
139
151
|
this.index--
|
|
140
|
-
this.
|
|
152
|
+
this.notify("position", { start: this.index })
|
|
141
153
|
}
|
|
142
154
|
|
|
143
155
|
/* search operations */
|
|
@@ -146,7 +158,7 @@ export class QueuePointer<T extends QueueElement> extends EventEmitter {
|
|
|
146
158
|
while (position < this.queue.elements.length
|
|
147
159
|
&& this.queue.elements[position].type !== type)
|
|
148
160
|
position++
|
|
149
|
-
this.
|
|
161
|
+
this.notify("search", { start: this.index, end: position })
|
|
150
162
|
return position
|
|
151
163
|
}
|
|
152
164
|
searchBackward (type: T["type"]) {
|
|
@@ -154,24 +166,24 @@ export class QueuePointer<T extends QueueElement> extends EventEmitter {
|
|
|
154
166
|
while (position > 0
|
|
155
167
|
&& this.queue.elements[position].type !== type)
|
|
156
168
|
position--
|
|
157
|
-
this.
|
|
169
|
+
this.notify("search", { start: position, end: this.index })
|
|
158
170
|
return position
|
|
159
171
|
}
|
|
160
172
|
|
|
161
173
|
/* reading operations */
|
|
162
|
-
peek (position?: number) {
|
|
174
|
+
peek (position?: number): T | undefined {
|
|
163
175
|
if (position === undefined)
|
|
164
176
|
position = this.index
|
|
165
177
|
position = Math.max(0, Math.min(position, this.queue.elements.length))
|
|
166
178
|
const element = this.queue.elements[position]
|
|
167
|
-
this.queue.
|
|
179
|
+
this.queue.notify("read", { start: position, end: position })
|
|
168
180
|
return element
|
|
169
181
|
}
|
|
170
|
-
read () {
|
|
182
|
+
read (): T | undefined {
|
|
171
183
|
const element = this.queue.elements[this.index]
|
|
172
184
|
if (this.index < this.queue.elements.length)
|
|
173
185
|
this.index++
|
|
174
|
-
this.queue.
|
|
186
|
+
this.queue.notify("read", { start: this.index - 1, end: this.index - 1 })
|
|
175
187
|
return element
|
|
176
188
|
}
|
|
177
189
|
slice (size?: number) {
|
|
@@ -186,30 +198,32 @@ export class QueuePointer<T extends QueueElement> extends EventEmitter {
|
|
|
186
198
|
slice = this.queue.elements.slice(this.index)
|
|
187
199
|
this.index = this.queue.elements.length
|
|
188
200
|
}
|
|
189
|
-
this.queue.
|
|
201
|
+
this.queue.notify("read", { start, end: this.index })
|
|
190
202
|
return slice
|
|
191
203
|
}
|
|
192
204
|
|
|
193
205
|
/* writing operations */
|
|
194
|
-
touch () {
|
|
195
|
-
if (
|
|
196
|
-
|
|
197
|
-
|
|
206
|
+
touch (position?: number) {
|
|
207
|
+
if (position === undefined)
|
|
208
|
+
position = this.index
|
|
209
|
+
if (position >= this.queue.elements.length)
|
|
210
|
+
throw new Error(`cannot touch after last element ${position} ${this.queue.elements.length}`)
|
|
211
|
+
this.queue.notify("write", { start: position, end: position, op: "touch" })
|
|
198
212
|
}
|
|
199
213
|
append (element: T) {
|
|
200
214
|
this.queue.elements.push(element)
|
|
201
215
|
this.index = this.queue.elements.length
|
|
202
|
-
this.queue.
|
|
216
|
+
this.queue.notify("write", { start: this.index - 1, end: this.index - 1, op: "append" })
|
|
203
217
|
}
|
|
204
218
|
insert (element: T) {
|
|
205
219
|
this.queue.elements.splice(this.index, 0, element)
|
|
206
|
-
this.queue.
|
|
220
|
+
this.queue.notify("write", { start: this.index, end: this.index, op: "insert" })
|
|
207
221
|
}
|
|
208
222
|
delete () {
|
|
209
223
|
if (this.index >= this.queue.elements.length)
|
|
210
224
|
throw new Error("cannot delete after last element")
|
|
211
225
|
this.queue.elements.splice(this.index, 1)
|
|
212
|
-
this.queue.
|
|
226
|
+
this.queue.notify("write", { start: this.index, end: this.index, op: "delete" })
|
|
213
227
|
}
|
|
214
228
|
}
|
|
215
229
|
|
|
@@ -217,10 +231,18 @@ export class QueuePointer<T extends QueueElement> extends EventEmitter {
|
|
|
217
231
|
export class Queue<T extends QueueElement> extends EventEmitter {
|
|
218
232
|
public elements: T[] = []
|
|
219
233
|
private pointers = new Map<string, QueuePointer<T>>()
|
|
234
|
+
private silence = false
|
|
220
235
|
constructor () {
|
|
221
236
|
super()
|
|
222
237
|
this.setMaxListeners(100)
|
|
223
238
|
}
|
|
239
|
+
silent (silence: boolean) {
|
|
240
|
+
this.silence = silence
|
|
241
|
+
}
|
|
242
|
+
notify (event: string, info: any) {
|
|
243
|
+
if (!this.silence)
|
|
244
|
+
this.emit(event, info)
|
|
245
|
+
}
|
|
224
246
|
pointerUse (name: string): QueuePointer<T> {
|
|
225
247
|
if (!this.pointers.has(name))
|
|
226
248
|
this.pointers.set(name, new QueuePointer<T>(name, this))
|
|
@@ -234,9 +256,10 @@ export class Queue<T extends QueueElement> extends EventEmitter {
|
|
|
234
256
|
trim (): void {
|
|
235
257
|
/* determine minimum pointer position */
|
|
236
258
|
let min = this.elements.length
|
|
237
|
-
for (const pointer of this.pointers.values())
|
|
259
|
+
for (const pointer of this.pointers.values()) {
|
|
238
260
|
if (min > pointer.position())
|
|
239
261
|
min = pointer.position()
|
|
262
|
+
}
|
|
240
263
|
|
|
241
264
|
/* trim the maximum amount of first elements */
|
|
242
265
|
if (min > 0) {
|
|
@@ -245,6 +268,8 @@ export class Queue<T extends QueueElement> extends EventEmitter {
|
|
|
245
268
|
/* shift all pointers */
|
|
246
269
|
for (const pointer of this.pointers.values())
|
|
247
270
|
pointer.position(pointer.position() - min)
|
|
271
|
+
|
|
272
|
+
this.notify("write", { start: 0, end: min, op: "trim" })
|
|
248
273
|
}
|
|
249
274
|
}
|
|
250
275
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|