@qvac/bci-whispercpp 0.0.0 → 0.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 +82 -0
- package/LICENSE +179 -0
- package/NOTICE +108 -0
- package/README.md +378 -0
- package/addonLogging.d.ts +7 -0
- package/addonLogging.js +6 -0
- package/bci.js +370 -0
- package/binding.js +1 -0
- package/configChecker.js +91 -0
- package/index.d.ts +134 -0
- package/index.js +676 -0
- package/lib/error.js +112 -0
- package/lib/stream.js +207 -0
- package/lib/util.js +15 -0
- package/lib/wer.js +40 -0
- package/package.json +96 -1
- package/prebuilds/android-arm64/qvac__bci-whispercpp.bare +0 -0
- package/prebuilds/darwin-arm64/qvac__bci-whispercpp.bare +0 -0
- package/prebuilds/darwin-arm64/qvac__bci-whispercpp.bare.exports +1439 -0
- package/prebuilds/darwin-x64/qvac__bci-whispercpp.bare +0 -0
- package/prebuilds/darwin-x64/qvac__bci-whispercpp.bare.exports +1654 -0
- package/prebuilds/ios-arm64/qvac__bci-whispercpp.bare +0 -0
- package/prebuilds/ios-arm64/qvac__bci-whispercpp.bare.exports +1439 -0
- package/prebuilds/ios-arm64-simulator/qvac__bci-whispercpp.bare +0 -0
- package/prebuilds/ios-arm64-simulator/qvac__bci-whispercpp.bare.exports +1439 -0
- package/prebuilds/ios-x64-simulator/qvac__bci-whispercpp.bare +0 -0
- package/prebuilds/ios-x64-simulator/qvac__bci-whispercpp.bare.exports +1654 -0
- package/prebuilds/linux-arm64/qvac__bci-whispercpp.bare +0 -0
- package/prebuilds/linux-x64/qvac__bci-whispercpp.bare +0 -0
- package/prebuilds/win32-x64/qvac__bci-whispercpp.bare +0 -0
- package/prebuilds/win32-x64/qvac__bci-whispercpp.bare.exports +0 -0
package/lib/error.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { QvacErrorBase, addCodes } = require('@qvac/error')
|
|
4
|
+
|
|
5
|
+
class QvacErrorAddonBCI extends QvacErrorBase { }
|
|
6
|
+
|
|
7
|
+
const { name, version } = require('../package.json')
|
|
8
|
+
|
|
9
|
+
// This library has error code range from 26001 to 27000.
|
|
10
|
+
// Ranges used elsewhere in the @qvac/error registry:
|
|
11
|
+
// 6001-6018 @qvac/transcription-whispercpp
|
|
12
|
+
// 7001-7011 @qvac/tts-onnx
|
|
13
|
+
// 8001-8008 @qvac/translation-nmtcpp
|
|
14
|
+
// 24001+ @qvac/transcription-parakeet
|
|
15
|
+
const ERR_CODES = Object.freeze({
|
|
16
|
+
FAILED_TO_LOAD_WEIGHTS: 26001,
|
|
17
|
+
FAILED_TO_CANCEL: 26002,
|
|
18
|
+
FAILED_TO_APPEND: 26003,
|
|
19
|
+
FAILED_TO_DESTROY: 26004,
|
|
20
|
+
FAILED_TO_ACTIVATE: 26005,
|
|
21
|
+
INVALID_NEURAL_INPUT: 26006,
|
|
22
|
+
JOB_ALREADY_RUNNING: 26007,
|
|
23
|
+
MODEL_NOT_LOADED: 26008,
|
|
24
|
+
MODEL_FILE_NOT_FOUND: 26009,
|
|
25
|
+
BUFFER_LIMIT_EXCEEDED: 26010,
|
|
26
|
+
FAILED_TO_START_JOB: 26011,
|
|
27
|
+
INVALID_CONFIG: 26012,
|
|
28
|
+
EMBEDDER_WEIGHTS_INVALID: 26013,
|
|
29
|
+
STREAM_ALREADY_ACTIVE: 26014,
|
|
30
|
+
INVALID_STREAM_INPUT: 26015,
|
|
31
|
+
INVALID_STREAM_HEADER: 26016,
|
|
32
|
+
WINDOW_TOO_LARGE: 26017
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
addCodes({
|
|
36
|
+
[ERR_CODES.FAILED_TO_LOAD_WEIGHTS]: {
|
|
37
|
+
name: 'FAILED_TO_LOAD_WEIGHTS',
|
|
38
|
+
message: (message) => `Failed to load weights, error: ${message}`
|
|
39
|
+
},
|
|
40
|
+
[ERR_CODES.FAILED_TO_CANCEL]: {
|
|
41
|
+
name: 'FAILED_TO_CANCEL',
|
|
42
|
+
message: (message) => `Failed to cancel inference, error: ${message}`
|
|
43
|
+
},
|
|
44
|
+
[ERR_CODES.FAILED_TO_APPEND]: {
|
|
45
|
+
name: 'FAILED_TO_APPEND',
|
|
46
|
+
message: (message) => `Failed to append data to processing queue, error: ${message}`
|
|
47
|
+
},
|
|
48
|
+
[ERR_CODES.FAILED_TO_DESTROY]: {
|
|
49
|
+
name: 'FAILED_TO_DESTROY',
|
|
50
|
+
message: (message) => `Failed to destroy instance, error: ${message}`
|
|
51
|
+
},
|
|
52
|
+
[ERR_CODES.FAILED_TO_ACTIVATE]: {
|
|
53
|
+
name: 'FAILED_TO_ACTIVATE',
|
|
54
|
+
message: (message) => `Failed to activate model, error: ${message}`
|
|
55
|
+
},
|
|
56
|
+
[ERR_CODES.INVALID_NEURAL_INPUT]: {
|
|
57
|
+
name: 'INVALID_NEURAL_INPUT',
|
|
58
|
+
message: (message) => `Invalid neural signal input: ${message}`
|
|
59
|
+
},
|
|
60
|
+
[ERR_CODES.JOB_ALREADY_RUNNING]: {
|
|
61
|
+
name: 'JOB_ALREADY_RUNNING',
|
|
62
|
+
message: () => 'Cannot set new job: a job is already set or being processed'
|
|
63
|
+
},
|
|
64
|
+
[ERR_CODES.MODEL_NOT_LOADED]: {
|
|
65
|
+
name: 'MODEL_NOT_LOADED',
|
|
66
|
+
message: () => 'Model is not loaded'
|
|
67
|
+
},
|
|
68
|
+
[ERR_CODES.MODEL_FILE_NOT_FOUND]: {
|
|
69
|
+
name: 'MODEL_FILE_NOT_FOUND',
|
|
70
|
+
message: (modelPath) => `Model file not found at: ${modelPath}`
|
|
71
|
+
},
|
|
72
|
+
[ERR_CODES.BUFFER_LIMIT_EXCEEDED]: {
|
|
73
|
+
name: 'BUFFER_LIMIT_EXCEEDED',
|
|
74
|
+
message: (limit) => `Neural signal buffer exceeded limit of ${limit}`
|
|
75
|
+
},
|
|
76
|
+
[ERR_CODES.FAILED_TO_START_JOB]: {
|
|
77
|
+
name: 'FAILED_TO_START_JOB',
|
|
78
|
+
message: (message) => `Failed to start inference job, error: ${message}`
|
|
79
|
+
},
|
|
80
|
+
[ERR_CODES.INVALID_CONFIG]: {
|
|
81
|
+
name: 'INVALID_CONFIG',
|
|
82
|
+
message: (message) => `Invalid BCI configuration: ${message}`
|
|
83
|
+
},
|
|
84
|
+
[ERR_CODES.EMBEDDER_WEIGHTS_INVALID]: {
|
|
85
|
+
name: 'EMBEDDER_WEIGHTS_INVALID',
|
|
86
|
+
message: (message) => `BCI embedder weights are invalid: ${message}`
|
|
87
|
+
},
|
|
88
|
+
[ERR_CODES.STREAM_ALREADY_ACTIVE]: {
|
|
89
|
+
name: 'STREAM_ALREADY_ACTIVE',
|
|
90
|
+
message: () => 'A streaming transcription is already in progress'
|
|
91
|
+
},
|
|
92
|
+
[ERR_CODES.INVALID_STREAM_INPUT]: {
|
|
93
|
+
name: 'INVALID_STREAM_INPUT',
|
|
94
|
+
message: (message) => `Invalid neural signal stream input: ${message}`
|
|
95
|
+
},
|
|
96
|
+
[ERR_CODES.INVALID_STREAM_HEADER]: {
|
|
97
|
+
name: 'INVALID_STREAM_HEADER',
|
|
98
|
+
message: (message) => `Invalid neural signal stream header: ${message}`
|
|
99
|
+
},
|
|
100
|
+
[ERR_CODES.WINDOW_TOO_LARGE]: {
|
|
101
|
+
name: 'WINDOW_TOO_LARGE',
|
|
102
|
+
message: (limit) => `Stream window size exceeds encoder capacity of ${limit} timesteps`
|
|
103
|
+
}
|
|
104
|
+
}, {
|
|
105
|
+
name,
|
|
106
|
+
version
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
module.exports = {
|
|
110
|
+
ERR_CODES,
|
|
111
|
+
QvacErrorAddonBCI
|
|
112
|
+
}
|
package/lib/stream.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { QvacErrorAddonBCI, ERR_CODES } = require('./error')
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Streaming helpers for the sliding-window transcription driver in index.js.
|
|
7
|
+
*
|
|
8
|
+
* These are pure functions kept separate from the class so they can be
|
|
9
|
+
* unit-tested in isolation (see test/unit/stitch.test.js) and so index.js
|
|
10
|
+
* stays focused on lifecycle orchestration.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Coerce a stream chunk into a Uint8Array without copying when possible.
|
|
15
|
+
* Throws INVALID_STREAM_INPUT if the chunk isn't a recognised binary form.
|
|
16
|
+
*/
|
|
17
|
+
function toUint8 (chunk) {
|
|
18
|
+
if (chunk instanceof Uint8Array) return chunk
|
|
19
|
+
if (ArrayBuffer.isView(chunk)) {
|
|
20
|
+
return new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength)
|
|
21
|
+
}
|
|
22
|
+
if (chunk instanceof ArrayBuffer) return new Uint8Array(chunk)
|
|
23
|
+
if (Array.isArray(chunk)) return Uint8Array.from(chunk)
|
|
24
|
+
throw new QvacErrorAddonBCI({
|
|
25
|
+
code: ERR_CODES.INVALID_STREAM_INPUT,
|
|
26
|
+
adds: 'stream yielded non-binary chunk'
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Copy out the byte range [startTs, endTs) from a list of body chunks
|
|
32
|
+
* (each element a Uint8Array) into a single contiguous Uint8Array.
|
|
33
|
+
* The caller owns timestep→byte translation via bytesPerTimestep.
|
|
34
|
+
*/
|
|
35
|
+
function sliceBody (bodyChunks, bytesPerTimestep, startTs, endTs, totalBytes) {
|
|
36
|
+
const startByte = startTs * bytesPerTimestep
|
|
37
|
+
const endByte = Math.min(endTs * bytesPerTimestep, totalBytes)
|
|
38
|
+
const out = new Uint8Array(Math.max(0, endByte - startByte))
|
|
39
|
+
if (out.byteLength === 0) return out
|
|
40
|
+
let offset = 0
|
|
41
|
+
let cursor = 0
|
|
42
|
+
for (const chunk of bodyChunks) {
|
|
43
|
+
const chunkStart = cursor
|
|
44
|
+
const chunkEnd = cursor + chunk.byteLength
|
|
45
|
+
cursor = chunkEnd
|
|
46
|
+
if (chunkEnd <= startByte) continue
|
|
47
|
+
if (chunkStart >= endByte) break
|
|
48
|
+
const from = Math.max(0, startByte - chunkStart)
|
|
49
|
+
const to = Math.min(chunk.byteLength, endByte - chunkStart)
|
|
50
|
+
out.set(chunk.subarray(from, to), offset)
|
|
51
|
+
offset += (to - from)
|
|
52
|
+
}
|
|
53
|
+
return out
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Build an [T, C] header-prefixed buffer the addon can consume as a single
|
|
58
|
+
* batch input, reusing the per-window body bytes.
|
|
59
|
+
*/
|
|
60
|
+
function buildWindowBuffer (windowBody, channels, windowTimesteps) {
|
|
61
|
+
const out = new Uint8Array(8 + windowBody.byteLength)
|
|
62
|
+
const view = new DataView(out.buffer, out.byteOffset, out.byteLength)
|
|
63
|
+
view.setUint32(0, windowTimesteps, true)
|
|
64
|
+
view.setUint32(4, channels, true)
|
|
65
|
+
out.set(windowBody, 8)
|
|
66
|
+
return out
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function normalizeWord (w) {
|
|
70
|
+
return w.toLowerCase().replace(/[^a-z0-9']/g, '')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Text-only word-level stitch: find the longest normalised-word suffix of
|
|
75
|
+
* `prevText` that also appears as a prefix of `newText`, and treat only
|
|
76
|
+
* the remainder of `newText` as fresh content.
|
|
77
|
+
*
|
|
78
|
+
* The streaming driver in index.js uses `stitchSegments` (below) because
|
|
79
|
+
* it needs to preserve per-segment timestamp metadata. `stitchMerge` is
|
|
80
|
+
* retained as a public text-only helper for callers that only have raw
|
|
81
|
+
* transcript strings to merge (e.g. post-hoc analysis, unit testing the
|
|
82
|
+
* overlap algorithm without constructing segment objects) and as the
|
|
83
|
+
* reference implementation of the underlying word-overlap logic.
|
|
84
|
+
*
|
|
85
|
+
* Returns { delta, merged, bestK }:
|
|
86
|
+
* - delta: the newly-discovered tail
|
|
87
|
+
* - merged: prevText extended by delta
|
|
88
|
+
* - bestK: number of words absorbed as overlap (for inspection/tests)
|
|
89
|
+
*
|
|
90
|
+
* maxWords caps the search depth so pathological inputs stay O(maxWords^2).
|
|
91
|
+
* Known limitation: legitimate immediate word repetitions at a window
|
|
92
|
+
* boundary (e.g. "the the") will collapse; acceptable for v1 sliding
|
|
93
|
+
* window until a segmentation model replaces this.
|
|
94
|
+
*/
|
|
95
|
+
function stitchMerge (prevText, newText, maxWords) {
|
|
96
|
+
const prevWords = prevText.trim().split(/\s+/).filter(Boolean)
|
|
97
|
+
const newWords = newText.trim().split(/\s+/).filter(Boolean)
|
|
98
|
+
|
|
99
|
+
if (prevWords.length === 0) {
|
|
100
|
+
return { delta: newWords.join(' '), merged: newWords.join(' '), bestK: 0 }
|
|
101
|
+
}
|
|
102
|
+
if (newWords.length === 0) {
|
|
103
|
+
return { delta: '', merged: prevWords.join(' '), bestK: 0 }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const bestK = _computeBestK(prevWords, newWords, maxWords)
|
|
107
|
+
const deltaWords = newWords.slice(bestK)
|
|
108
|
+
const delta = deltaWords.join(' ')
|
|
109
|
+
const merged = [...prevWords, ...deltaWords].join(' ')
|
|
110
|
+
return { delta, merged, bestK }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Segment-aware variant of stitchMerge: preserves the per-segment
|
|
115
|
+
* timestamp/metadata fields emitted by the native decoder.
|
|
116
|
+
*
|
|
117
|
+
* `segments` is the array returned by a per-window decode (each entry is
|
|
118
|
+
* a whisper-style `{ text, t0, t1, ... }`). We run the same word-level
|
|
119
|
+
* overlap detection but trim the leading `bestK` words from the incoming
|
|
120
|
+
* segments rather than flattening to a single string.
|
|
121
|
+
*
|
|
122
|
+
* `windowStartTimestep` is attached to every emitted segment so
|
|
123
|
+
* consumers can correlate a window-local `t0`/`t1` back to the absolute
|
|
124
|
+
* position in the input stream.
|
|
125
|
+
*
|
|
126
|
+
* Returns:
|
|
127
|
+
* - deltaSegments: trimmed segments the driver should emit as an update
|
|
128
|
+
* (segments fully absorbed by the overlap are dropped; a segment
|
|
129
|
+
* partially overlapped gets its `text` rewritten to the surviving
|
|
130
|
+
* tail words).
|
|
131
|
+
* - merged: running transcript text (identical to stitchMerge.merged)
|
|
132
|
+
* - bestK: for inspection / tests
|
|
133
|
+
*/
|
|
134
|
+
function stitchSegments (prevText, segments, maxWords, windowStartTimestep = 0) {
|
|
135
|
+
const prevWords = prevText.trim().split(/\s+/).filter(Boolean)
|
|
136
|
+
|
|
137
|
+
const perSegment = []
|
|
138
|
+
const newWords = []
|
|
139
|
+
for (const seg of segments) {
|
|
140
|
+
const text = (seg && typeof seg.text === 'string') ? seg.text : ''
|
|
141
|
+
const words = text.trim().split(/\s+/).filter(Boolean)
|
|
142
|
+
perSegment.push({ seg, words })
|
|
143
|
+
for (const w of words) newWords.push(w)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (newWords.length === 0) {
|
|
147
|
+
return { deltaSegments: [], merged: prevWords.join(' '), bestK: 0 }
|
|
148
|
+
}
|
|
149
|
+
if (prevWords.length === 0) {
|
|
150
|
+
const deltaSegments = perSegment
|
|
151
|
+
.filter(({ words }) => words.length > 0)
|
|
152
|
+
.map(({ seg }) => ({ ...seg, windowStartTimestep }))
|
|
153
|
+
return { deltaSegments, merged: newWords.join(' '), bestK: 0 }
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const bestK = _computeBestK(prevWords, newWords, maxWords)
|
|
157
|
+
|
|
158
|
+
let toSkip = bestK
|
|
159
|
+
const deltaSegments = []
|
|
160
|
+
for (const { seg, words } of perSegment) {
|
|
161
|
+
if (words.length === 0) continue
|
|
162
|
+
if (toSkip >= words.length) { toSkip -= words.length; continue }
|
|
163
|
+
if (toSkip === 0) {
|
|
164
|
+
deltaSegments.push({ ...seg, windowStartTimestep })
|
|
165
|
+
} else {
|
|
166
|
+
const remainingWords = words.slice(toSkip)
|
|
167
|
+
const hasT0 = typeof seg.t0 === 'number'
|
|
168
|
+
const hasT1 = typeof seg.t1 === 'number'
|
|
169
|
+
const approxT0 = (hasT0 && hasT1)
|
|
170
|
+
? seg.t0 + Math.round((seg.t1 - seg.t0) * (toSkip / words.length))
|
|
171
|
+
: seg.t0
|
|
172
|
+
deltaSegments.push({
|
|
173
|
+
...seg,
|
|
174
|
+
text: remainingWords.join(' '),
|
|
175
|
+
t0: approxT0,
|
|
176
|
+
windowStartTimestep
|
|
177
|
+
})
|
|
178
|
+
toSkip = 0
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const merged = [...prevWords, ...newWords.slice(bestK)].join(' ')
|
|
183
|
+
return { deltaSegments, merged, bestK }
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function _computeBestK (prevWords, newWords, maxWords) {
|
|
187
|
+
const maxK = Math.min(prevWords.length, newWords.length, maxWords)
|
|
188
|
+
for (let k = maxK; k >= 1; k--) {
|
|
189
|
+
let match = true
|
|
190
|
+
for (let i = 0; i < k; i++) {
|
|
191
|
+
const a = normalizeWord(prevWords[prevWords.length - k + i])
|
|
192
|
+
const b = normalizeWord(newWords[i])
|
|
193
|
+
if (a.length === 0 || b.length === 0 || a !== b) { match = false; break }
|
|
194
|
+
}
|
|
195
|
+
if (match) return k
|
|
196
|
+
}
|
|
197
|
+
return 0
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
module.exports = {
|
|
201
|
+
toUint8,
|
|
202
|
+
sliceBody,
|
|
203
|
+
buildWindowBuffer,
|
|
204
|
+
normalizeWord,
|
|
205
|
+
stitchMerge,
|
|
206
|
+
stitchSegments
|
|
207
|
+
}
|
package/lib/util.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
function flattenSegments (output) {
|
|
4
|
+
const segments = []
|
|
5
|
+
for (const entry of output) {
|
|
6
|
+
if (Array.isArray(entry)) {
|
|
7
|
+
segments.push(...entry)
|
|
8
|
+
} else if (entry && typeof entry.text === 'string') {
|
|
9
|
+
segments.push(entry)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return segments
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = { flattenSegments }
|
package/lib/wer.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Compute Word Error Rate between hypothesis and reference.
|
|
5
|
+
* Uses Levenshtein distance on word sequences.
|
|
6
|
+
* @param {string} hypothesis
|
|
7
|
+
* @param {string} reference
|
|
8
|
+
* @returns {number} WER as a ratio (0.0 = perfect, 1.0 = 100% errors)
|
|
9
|
+
*/
|
|
10
|
+
function computeWER (hypothesis, reference) {
|
|
11
|
+
const hyp = hypothesis.toLowerCase().trim().split(/\s+/).filter(Boolean)
|
|
12
|
+
const ref = reference.toLowerCase().trim().split(/\s+/).filter(Boolean)
|
|
13
|
+
|
|
14
|
+
if (ref.length === 0) return hyp.length === 0 ? 0 : 1
|
|
15
|
+
|
|
16
|
+
const n = ref.length
|
|
17
|
+
const m = hyp.length
|
|
18
|
+
const dp = Array.from({ length: n + 1 }, () => Array(m + 1).fill(0))
|
|
19
|
+
|
|
20
|
+
for (let i = 0; i <= n; i++) dp[i][0] = i
|
|
21
|
+
for (let j = 0; j <= m; j++) dp[0][j] = j
|
|
22
|
+
|
|
23
|
+
for (let i = 1; i <= n; i++) {
|
|
24
|
+
for (let j = 1; j <= m; j++) {
|
|
25
|
+
if (ref[i - 1] === hyp[j - 1]) {
|
|
26
|
+
dp[i][j] = dp[i - 1][j - 1]
|
|
27
|
+
} else {
|
|
28
|
+
dp[i][j] = 1 + Math.min(
|
|
29
|
+
dp[i - 1][j],
|
|
30
|
+
dp[i][j - 1],
|
|
31
|
+
dp[i - 1][j - 1]
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return dp[n][m] / n
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = { computeWER }
|
package/package.json
CHANGED
|
@@ -1 +1,96 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"name": "@qvac/bci-whispercpp",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Brain-Computer Interface (BCI) neural signal transcription addon for qvac, powered by whisper.cpp",
|
|
5
|
+
"addon": true,
|
|
6
|
+
"engines": {
|
|
7
|
+
"bare": ">=1.24.0"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"lint": "standard \"examples/**/*.js\" \"test/**/*.js\" \"*.js\" \"lib/**/*.js\"",
|
|
11
|
+
"lint:fix": "standard --fix \"examples/**/*.js\" \"test/**/*.js\" \"*.js\" \"lib/**/*.js\"",
|
|
12
|
+
"lint-cpp": "clang-tidy -p build addon/src/js-interface/JSAdapter.cpp addon/src/js-interface/binding.cpp addon/src/model-interface/bci/BCIConfig.cpp addon/src/model-interface/bci/BCIModel.cpp addon/src/model-interface/bci/NeuralProcessor.cpp addon/src/addon/AddonJs.hpp addon/src/js-interface/JSAdapter.hpp addon/src/addon/BCIErrors.hpp addon/src/model-interface/BCITypes.hpp addon/src/model-interface/bci/BCIConfig.hpp addon/src/model-interface/bci/BCIModel.hpp addon/src/model-interface/bci/NeuralProcessor.hpp",
|
|
13
|
+
"build": "bare-make generate && bare-make build && bare-make install",
|
|
14
|
+
"build:pack": "mkdir -p dist && npm pack --pack-destination dist",
|
|
15
|
+
"test": "npm run test:unit && npm run test:integration",
|
|
16
|
+
"test:unit": "brittle-bare test/unit/*.test.js",
|
|
17
|
+
"test:integration": "brittle-bare test/integration/addon.test.js",
|
|
18
|
+
"test:cpp:build": "bare-make generate -D BUILD_TESTING=ON && bare-make build --target test-bci-core && bare-make install",
|
|
19
|
+
"test:cpp:run": "cd build/addon/tests/ && LLVM_PROFILE_FILE=default.profraw ./test-bci-core --gtest_output=xml:cpp-test-results.xml",
|
|
20
|
+
"test:cpp": "npm run test:cpp:build && npm run test:cpp:run",
|
|
21
|
+
"coverage:cpp:build": "bare-make generate -D BUILD_TESTING=ON -D ENABLE_COVERAGE=ON && bare-make build --target test-bci-core",
|
|
22
|
+
"coverage:cpp:summary": "cd build/addon/tests && llvm-cov-19 report ./test-bci-core --instr-profile=coverage.profdata -ignore-filename-regex='(tests|build|node_modules|gtest|gmock|\\.vcpkg|/usr)/' > coverage-summary.txt",
|
|
23
|
+
"coverage:cpp:report": "cd build/addon/tests/ && ls -lha && llvm-profdata-19 merge -sparse default.profraw -o coverage.profdata && llvm-cov-19 show ./test-bci-core -instr-profile=coverage.profdata -format=html -output-dir=coverage-html -ignore-filename-regex='(tests|build|node_modules|gtest|gmock|\\.vcpkg|/usr)/' && llvm-cov-19 export ./test-bci-core -instr-profile=coverage.profdata -format=lcov -ignore-filename-regex='(tests|build|node_modules|gtest|gmock|\\.vcpkg|/usr)/' > lcov.info && npm run coverage:cpp:summary",
|
|
24
|
+
"coverage:cpp": "npm run coverage:cpp:build && npm run test:cpp:run && npm run coverage:cpp:report",
|
|
25
|
+
"test:dts": "tsc -p tsconfig.dts.json",
|
|
26
|
+
"test:mobile:generate": "node scripts/generate-mobile-tests.js"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"addonLogging.js",
|
|
30
|
+
"addonLogging.d.ts",
|
|
31
|
+
"binding.js",
|
|
32
|
+
"bci.js",
|
|
33
|
+
"configChecker.js",
|
|
34
|
+
"index.js",
|
|
35
|
+
"index.d.ts",
|
|
36
|
+
"prebuilds",
|
|
37
|
+
"lib",
|
|
38
|
+
"README.md",
|
|
39
|
+
"CHANGELOG.md",
|
|
40
|
+
"LICENSE",
|
|
41
|
+
"NOTICE"
|
|
42
|
+
],
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "git+https://github.com/tetherto/qvac.git",
|
|
46
|
+
"directory": "packages/bci-whispercpp"
|
|
47
|
+
},
|
|
48
|
+
"author": "Tether",
|
|
49
|
+
"keywords": [
|
|
50
|
+
"tether",
|
|
51
|
+
"addon",
|
|
52
|
+
"whisper",
|
|
53
|
+
"bci",
|
|
54
|
+
"brain-computer-interface",
|
|
55
|
+
"neural",
|
|
56
|
+
"qvac"
|
|
57
|
+
],
|
|
58
|
+
"license": "Apache-2.0",
|
|
59
|
+
"bugs": "https://github.com/tetherto/qvac/issues",
|
|
60
|
+
"homepage": "https://github.com/tetherto/qvac/tree/main/packages/bci-whispercpp#readme",
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@types/node": "^22.15.3",
|
|
63
|
+
"bare-buffer": "^3.4.2",
|
|
64
|
+
"brittle": "^3.17.0",
|
|
65
|
+
"cmake-bare": "^1.7.5",
|
|
66
|
+
"cmake-vcpkg": "^1.1.0",
|
|
67
|
+
"standard": "^17.1.2",
|
|
68
|
+
"typescript": "^5.9.2"
|
|
69
|
+
},
|
|
70
|
+
"dependencies": {
|
|
71
|
+
"@qvac/error": "^0.1.0",
|
|
72
|
+
"@qvac/infer-base": "^0.4.0",
|
|
73
|
+
"@qvac/logging": "^0.1.0",
|
|
74
|
+
"bare-fs": "^4.5.1",
|
|
75
|
+
"bare-path": "^3.0.0"
|
|
76
|
+
},
|
|
77
|
+
"exports": {
|
|
78
|
+
"./package": "./package.json",
|
|
79
|
+
".": {
|
|
80
|
+
"types": "./index.d.ts",
|
|
81
|
+
"default": "./index.js"
|
|
82
|
+
},
|
|
83
|
+
"./addonLogging": {
|
|
84
|
+
"types": "./addonLogging.d.ts",
|
|
85
|
+
"default": "./addonLogging.js"
|
|
86
|
+
},
|
|
87
|
+
"./addonLogging.js": "./addonLogging.js",
|
|
88
|
+
"./bci": "./bci.js",
|
|
89
|
+
"./bci.js": "./bci.js",
|
|
90
|
+
"./binding": "./binding.js",
|
|
91
|
+
"./binding.js": "./binding.js",
|
|
92
|
+
"./util": "./lib/util.js",
|
|
93
|
+
"./wer": "./lib/wer.js"
|
|
94
|
+
},
|
|
95
|
+
"types": "index.d.ts"
|
|
96
|
+
}
|
|
Binary file
|
|
Binary file
|