@neurodevs/node-lsl 17.1.0 → 18.0.0
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/.claude/settings.local.json +10 -0
- package/.regressionproof.json +7 -0
- package/.vscode/launch.json +3 -2
- package/README.md +9 -35
- package/build/.spruce/settings.json +5 -1
- package/build/__tests__/AbstractPackageTest.d.ts +4 -4
- package/build/__tests__/AbstractPackageTest.js +9 -11
- package/build/__tests__/AbstractPackageTest.js.map +1 -1
- package/build/__tests__/impl/LslBackpressureMonitor.test.d.ts +7 -0
- package/build/__tests__/impl/LslBackpressureMonitor.test.js +26 -0
- package/build/__tests__/impl/LslBackpressureMonitor.test.js.map +1 -0
- package/build/__tests__/impl/LslEventMarkerEmitter.test.d.ts +34 -0
- package/build/__tests__/impl/LslEventMarkerEmitter.test.js +243 -0
- package/build/__tests__/impl/LslEventMarkerEmitter.test.js.map +1 -0
- package/build/__tests__/impl/LslStreamInfo.test.d.ts +0 -3
- package/build/__tests__/impl/LslStreamInfo.test.js +2 -27
- package/build/__tests__/impl/LslStreamInfo.test.js.map +1 -1
- package/build/__tests__/impl/LslStreamInlet.test.d.ts +22 -8
- package/build/__tests__/impl/LslStreamInlet.test.js +240 -66
- package/build/__tests__/impl/LslStreamInlet.test.js.map +1 -1
- package/build/__tests__/impl/LslStreamOutlet.test.d.ts +3 -7
- package/build/__tests__/impl/LslStreamOutlet.test.js +36 -46
- package/build/__tests__/impl/LslStreamOutlet.test.js.map +1 -1
- package/build/__tests__/impl/LslWebSocketBridge.test.js +16 -17
- package/build/__tests__/impl/LslWebSocketBridge.test.js.map +1 -1
- package/build/assertions.d.ts +1 -1
- package/build/assertions.js.map +1 -1
- package/build/impl/LslBackpressureMonitor.d.ts +8 -0
- package/build/impl/LslBackpressureMonitor.js +8 -0
- package/build/impl/LslBackpressureMonitor.js.map +1 -0
- package/build/impl/LslEventMarkerEmitter.d.ts +35 -0
- package/build/impl/LslEventMarkerEmitter.js +86 -0
- package/build/impl/LslEventMarkerEmitter.js.map +1 -0
- package/build/impl/LslStreamInfo.d.ts +4 -8
- package/build/impl/LslStreamInfo.js +8 -21
- package/build/impl/LslStreamInfo.js.map +1 -1
- package/build/impl/LslStreamInlet.d.ts +31 -47
- package/build/impl/LslStreamInlet.js +82 -143
- package/build/impl/LslStreamInlet.js.map +1 -1
- package/build/impl/LslStreamOutlet.d.ts +15 -18
- package/build/impl/LslStreamOutlet.js +39 -47
- package/build/impl/LslStreamOutlet.js.map +1 -1
- package/build/impl/LslWebSocketBridge.d.ts +4 -5
- package/build/impl/LslWebSocketBridge.js +8 -8
- package/build/impl/LslWebSocketBridge.js.map +1 -1
- package/build/impl/workers/inlet/LslStreamInlet.worker.d.ts +22 -0
- package/build/impl/workers/inlet/LslStreamInlet.worker.js +42 -0
- package/build/impl/workers/inlet/LslStreamInlet.worker.js.map +1 -0
- package/build/impl/workers/inlet/StreamInletWorker.d.ts +73 -0
- package/build/impl/workers/inlet/StreamInletWorker.js +253 -0
- package/build/impl/workers/inlet/StreamInletWorker.js.map +1 -0
- package/build/impl/workers/outlet/LslStreamOutlet.worker.d.ts +32 -0
- package/build/impl/workers/outlet/LslStreamOutlet.worker.js +82 -0
- package/build/impl/workers/outlet/LslStreamOutlet.worker.js.map +1 -0
- package/build/index.d.ts +12 -14
- package/build/index.js +14 -16
- package/build/index.js.map +1 -1
- package/build/scripts/finalizeHandles.d.ts +2 -0
- package/build/scripts/finalizeHandles.js +48 -0
- package/build/scripts/finalizeHandles.js.map +1 -0
- package/build/scripts/runLslEventMarkerEmitter.js +11 -0
- package/build/scripts/runLslEventMarkerEmitter.js.map +1 -0
- package/build/scripts/runOutletToInlet.js +7 -6
- package/build/scripts/runOutletToInlet.js.map +1 -1
- package/build/scripts/testDestroyOutlet.d.ts +1 -0
- package/build/scripts/testDestroyOutlet.js +4 -0
- package/build/scripts/testDestroyOutlet.js.map +1 -0
- package/build/testDoubles/BackpressureMonitor/FakeBackpressureMonitor.d.ts +6 -0
- package/build/testDoubles/BackpressureMonitor/FakeBackpressureMonitor.js +10 -0
- package/build/testDoubles/BackpressureMonitor/FakeBackpressureMonitor.js.map +1 -0
- package/build/testDoubles/EventMarkerEmitter/FakeEventMarkerEmitter.d.ts +17 -0
- package/build/testDoubles/EventMarkerEmitter/FakeEventMarkerEmitter.js +30 -0
- package/build/testDoubles/EventMarkerEmitter/FakeEventMarkerEmitter.js.map +1 -0
- package/build/testDoubles/EventMarkerEmitter/SpyEventMarkerEmitter.d.ts +10 -0
- package/build/testDoubles/EventMarkerEmitter/SpyEventMarkerEmitter.js +23 -0
- package/build/testDoubles/EventMarkerEmitter/SpyEventMarkerEmitter.js.map +1 -0
- package/build/testDoubles/EventMarkerEmitter/ThrowingEventMarkerEmitter.d.ts +9 -0
- package/build/testDoubles/EventMarkerEmitter/ThrowingEventMarkerEmitter.js +19 -0
- package/build/testDoubles/EventMarkerEmitter/ThrowingEventMarkerEmitter.js.map +1 -0
- package/build/testDoubles/StreamInfo/FakeStreamInfo.d.ts +3 -3
- package/build/testDoubles/StreamInfo/FakeStreamInfo.js +3 -3
- package/build/testDoubles/StreamInfo/FakeStreamInfo.js.map +1 -1
- package/build/testDoubles/StreamInlet/FakeStreamInlet.d.ts +8 -10
- package/build/testDoubles/StreamInlet/FakeStreamInlet.js +6 -7
- package/build/testDoubles/StreamInlet/FakeStreamInlet.js.map +1 -1
- package/build/testDoubles/StreamInlet/SpyStreamInlet.d.ts +2 -5
- package/build/testDoubles/StreamInlet/SpyStreamInlet.js +2 -8
- package/build/testDoubles/StreamInlet/SpyStreamInlet.js.map +1 -1
- package/build/testDoubles/StreamOutlet/FakeStreamOutlet.d.ts +7 -9
- package/build/testDoubles/StreamOutlet/FakeStreamOutlet.js +5 -8
- package/build/testDoubles/StreamOutlet/FakeStreamOutlet.js.map +1 -1
- package/build/testDoubles/WebSocketBridge/FakeWebSocketBridge.d.ts +1 -1
- package/build/testDoubles/WebSocketBridge/FakeWebSocketBridge.js +1 -1
- package/build/testDoubles/WebSocketBridge/FakeWebSocketBridge.js.map +1 -1
- package/build/testDoubles/WorkerThreads/FakeWorker.d.ts +27 -0
- package/build/testDoubles/WorkerThreads/FakeWorker.js +67 -0
- package/build/testDoubles/WorkerThreads/FakeWorker.js.map +1 -0
- package/build/validations.js +0 -2
- package/build/validations.js.map +1 -1
- package/package.json +24 -8
- package/src/.spruce/settings.json +5 -1
- package/src/__tests__/AbstractPackageTest.ts +13 -15
- package/src/__tests__/impl/LslBackpressureMonitor.test.ts +25 -0
- package/src/__tests__/impl/LslEventMarkerEmitter.test.ts +322 -0
- package/src/__tests__/impl/LslStreamInfo.test.ts +3 -40
- package/src/__tests__/impl/LslStreamInlet.test.ts +366 -95
- package/src/__tests__/impl/LslStreamOutlet.test.ts +61 -69
- package/src/__tests__/impl/LslWebSocketBridge.test.ts +18 -18
- package/src/assertions.ts +2 -1
- package/src/impl/LslBackpressureMonitor.ts +13 -0
- package/src/impl/LslEventMarkerEmitter.ts +133 -0
- package/src/impl/LslStreamInfo.ts +17 -29
- package/src/impl/LslStreamInlet.ts +105 -208
- package/src/impl/LslStreamOutlet.ts +54 -78
- package/src/impl/LslWebSocketBridge.ts +12 -12
- package/src/impl/workers/inlet/LslStreamInlet.worker.ts +74 -0
- package/src/impl/workers/inlet/StreamInletWorker.ts +348 -0
- package/src/impl/workers/outlet/LslStreamOutlet.worker.ts +144 -0
- package/src/index.ts +14 -17
- package/src/scripts/finalizeHandles.ts +63 -0
- package/src/scripts/runLslEventMarkerEmitter.ts +15 -0
- package/src/scripts/runOutletToInlet.ts +10 -8
- package/src/scripts/testDestroyOutlet.ts +5 -0
- package/src/testDoubles/BackpressureMonitor/FakeBackpressureMonitor.ts +13 -0
- package/src/testDoubles/EventMarkerEmitter/FakeEventMarkerEmitter.ts +46 -0
- package/src/testDoubles/EventMarkerEmitter/SpyEventMarkerEmitter.ts +31 -0
- package/src/testDoubles/EventMarkerEmitter/ThrowingEventMarkerEmitter.ts +29 -0
- package/src/testDoubles/StreamInfo/FakeStreamInfo.ts +4 -4
- package/src/testDoubles/StreamInlet/FakeStreamInlet.ts +11 -17
- package/src/testDoubles/StreamInlet/SpyStreamInlet.ts +3 -16
- package/src/testDoubles/StreamOutlet/FakeStreamOutlet.ts +12 -15
- package/src/testDoubles/WebSocketBridge/FakeWebSocketBridge.ts +1 -1
- package/src/testDoubles/WorkerThreads/FakeWorker.ts +99 -0
- package/src/validations.ts +0 -2
- package/build/__tests__/impl/LiblslAdapter.test.d.ts +0 -79
- package/build/__tests__/impl/LiblslAdapter.test.js +0 -688
- package/build/__tests__/impl/LiblslAdapter.test.js.map +0 -1
- package/build/__tests__/impl/LslEventMarkerOutlet.test.d.ts +0 -21
- package/build/__tests__/impl/LslEventMarkerOutlet.test.js +0 -160
- package/build/__tests__/impl/LslEventMarkerOutlet.test.js.map +0 -1
- package/build/handleError.d.ts +0 -8
- package/build/handleError.js +0 -25
- package/build/handleError.js.map +0 -1
- package/build/impl/LiblslAdapter.d.ts +0 -159
- package/build/impl/LiblslAdapter.js +0 -317
- package/build/impl/LiblslAdapter.js.map +0 -1
- package/build/impl/LslEventMarkerOutlet.d.ts +0 -28
- package/build/impl/LslEventMarkerOutlet.js +0 -63
- package/build/impl/LslEventMarkerOutlet.js.map +0 -1
- package/build/scripts/runLslEventMarkerOutlet.js +0 -18
- package/build/scripts/runLslEventMarkerOutlet.js.map +0 -1
- package/build/testDoubles/EventMarkerOutlet/FakeEventMarkerOutlet.d.ts +0 -13
- package/build/testDoubles/EventMarkerOutlet/FakeEventMarkerOutlet.js +0 -26
- package/build/testDoubles/EventMarkerOutlet/FakeEventMarkerOutlet.js.map +0 -1
- package/build/testDoubles/EventMarkerOutlet/MockEventMarkerOutlet.d.ts +0 -17
- package/build/testDoubles/EventMarkerOutlet/MockEventMarkerOutlet.js +0 -46
- package/build/testDoubles/EventMarkerOutlet/MockEventMarkerOutlet.js.map +0 -1
- package/build/testDoubles/EventMarkerOutlet/SpyEventMarkerOutlet.d.ts +0 -9
- package/build/testDoubles/EventMarkerOutlet/SpyEventMarkerOutlet.js +0 -19
- package/build/testDoubles/EventMarkerOutlet/SpyEventMarkerOutlet.js.map +0 -1
- package/build/testDoubles/EventMarkerOutlet/ThrowingEventMarkerOutlet.d.ts +0 -9
- package/build/testDoubles/EventMarkerOutlet/ThrowingEventMarkerOutlet.js +0 -19
- package/build/testDoubles/EventMarkerOutlet/ThrowingEventMarkerOutlet.js.map +0 -1
- package/build/testDoubles/Liblsl/FakeLiblsl.d.ts +0 -50
- package/build/testDoubles/Liblsl/FakeLiblsl.js +0 -126
- package/build/testDoubles/Liblsl/FakeLiblsl.js.map +0 -1
- package/src/__tests__/impl/LiblslAdapter.test.ts +0 -817
- package/src/__tests__/impl/LslEventMarkerOutlet.test.ts +0 -195
- package/src/handleError.ts +0 -24
- package/src/impl/LiblslAdapter.ts +0 -548
- package/src/impl/LslEventMarkerOutlet.ts +0 -97
- package/src/scripts/runLslEventMarkerOutlet.ts +0 -23
- package/src/testDoubles/EventMarkerOutlet/FakeEventMarkerOutlet.ts +0 -36
- package/src/testDoubles/EventMarkerOutlet/MockEventMarkerOutlet.ts +0 -75
- package/src/testDoubles/EventMarkerOutlet/SpyEventMarkerOutlet.ts +0 -24
- package/src/testDoubles/EventMarkerOutlet/ThrowingEventMarkerOutlet.ts +0 -28
- package/src/testDoubles/Liblsl/FakeLiblsl.ts +0 -182
- /package/build/scripts/{runLslEventMarkerOutlet.d.ts → runLslEventMarkerEmitter.d.ts} +0 -0
|
@@ -1,275 +1,172 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import handleError from '../handleError.js'
|
|
4
|
-
import { BoundInlet } from './LiblslAdapter.js'
|
|
5
|
-
import LiblslAdapter from './LiblslAdapter.js'
|
|
6
|
-
import { StreamInfo } from './LslStreamInfo.js'
|
|
1
|
+
import { Worker } from 'node:worker_threads'
|
|
7
2
|
|
|
8
3
|
export default class LslStreamInlet implements StreamInlet {
|
|
9
4
|
public static Class?: StreamInletConstructor
|
|
5
|
+
public static Worker = Worker
|
|
6
|
+
public static waitAfterOpenStreamMs = 100
|
|
10
7
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
protected info: StreamInfo
|
|
14
|
-
private channelCount: number
|
|
8
|
+
private sourceId: string
|
|
15
9
|
private chunkSize: number
|
|
16
10
|
private maxBufferedMs: number
|
|
17
|
-
private
|
|
11
|
+
private pullTimeoutMs: number
|
|
12
|
+
private openStreamTimeoutMs: number
|
|
13
|
+
private waitAfterOpenStreamMs: number
|
|
14
|
+
private waitBetweenPullsMs: number
|
|
15
|
+
private flushInletOnStop: boolean
|
|
18
16
|
private onData: OnDataCallback
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
private pullDataMethod!: () => {
|
|
23
|
-
samples: Float32Array | undefined
|
|
24
|
-
timestamps: Float64Array | undefined
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
private dataBuffer!: Buffer<ArrayBuffer>
|
|
28
|
-
private dataBufferPtr!: JsExternal
|
|
29
|
-
|
|
30
|
-
private timestampBuffer!: Buffer<ArrayBuffer>
|
|
31
|
-
private timestampBufferPtr!: JsExternal
|
|
32
|
-
|
|
33
|
-
private errorCodeBuffer!: Buffer<ArrayBuffer>
|
|
34
|
-
private errorCodeBufferPtr!: JsExternal
|
|
18
|
+
private worker!: Worker
|
|
19
|
+
private workerReady = false
|
|
35
20
|
|
|
36
21
|
private readonly sixMinutesInMs = 360 * 1000
|
|
22
|
+
private readonly aboutOneYearInMs = 32000000 * 1000
|
|
37
23
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
protected constructor(
|
|
41
|
-
info: StreamInfo,
|
|
42
|
-
options: StreamInletConstructorOptions,
|
|
43
|
-
onData: OnDataCallback
|
|
44
|
-
) {
|
|
45
|
-
const { chunkSize, maxBufferedMs, timeoutMs } = options ?? {}
|
|
24
|
+
public isRunning = false
|
|
46
25
|
|
|
47
|
-
|
|
48
|
-
|
|
26
|
+
protected constructor(options: StreamInletOptions, onData: OnDataCallback) {
|
|
27
|
+
const {
|
|
28
|
+
sourceId,
|
|
29
|
+
chunkSize,
|
|
30
|
+
maxBufferedMs,
|
|
31
|
+
pullTimeoutMs,
|
|
32
|
+
openStreamTimeoutMs,
|
|
33
|
+
waitAfterOpenStreamMs,
|
|
34
|
+
waitBetweenPullsMs,
|
|
35
|
+
flushInletOnStop,
|
|
36
|
+
} = options ?? {}
|
|
37
|
+
|
|
38
|
+
this.sourceId = sourceId
|
|
49
39
|
this.chunkSize = chunkSize
|
|
50
40
|
this.maxBufferedMs = maxBufferedMs ?? this.sixMinutesInMs
|
|
51
|
-
this.
|
|
41
|
+
this.pullTimeoutMs = pullTimeoutMs ?? 0
|
|
42
|
+
this.openStreamTimeoutMs = openStreamTimeoutMs ?? this.aboutOneYearInMs
|
|
43
|
+
this.waitAfterOpenStreamMs = waitAfterOpenStreamMs ?? this.defaultWaitMs
|
|
44
|
+
this.waitBetweenPullsMs = waitBetweenPullsMs ?? 1
|
|
45
|
+
this.flushInletOnStop = flushInletOnStop ?? true
|
|
52
46
|
this.onData = onData
|
|
53
47
|
|
|
54
|
-
this.
|
|
55
|
-
this.createBoundInlet()
|
|
48
|
+
this.createWorkerThread()
|
|
56
49
|
}
|
|
57
50
|
|
|
58
51
|
public static async Create(
|
|
59
52
|
options: StreamInletOptions,
|
|
60
53
|
onData: OnDataCallback
|
|
61
54
|
) {
|
|
62
|
-
|
|
63
|
-
return new (this.Class ?? this)(info, options, onData)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
private setPullDataMethod() {
|
|
67
|
-
if (this.chunkSize === 1) {
|
|
68
|
-
this.pullDataMethod = this.pullSample
|
|
69
|
-
} else {
|
|
70
|
-
this.pullDataMethod = this.pullChunk
|
|
71
|
-
}
|
|
55
|
+
return new (this.Class ?? this)(options, onData)
|
|
72
56
|
}
|
|
73
57
|
|
|
74
|
-
private
|
|
75
|
-
this.
|
|
76
|
-
|
|
77
|
-
maxBufferedMs: this.maxBufferedMs,
|
|
78
|
-
})
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
public startPulling() {
|
|
82
|
-
this.isRunning = true
|
|
83
|
-
|
|
84
|
-
this.createWriteableBuffers()
|
|
85
|
-
this.createPointersToBuffers()
|
|
86
|
-
|
|
87
|
-
void this.pullOnLoop()
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
private createWriteableBuffers() {
|
|
91
|
-
this.createDataBuffer()
|
|
92
|
-
this.createTimestampBuffer()
|
|
93
|
-
this.createErrorCodeBuffer()
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
private createDataBuffer() {
|
|
97
|
-
this.dataBuffer = Buffer.alloc(
|
|
98
|
-
this.bytesPerFloat * this.chunkSize * this.channelCount
|
|
58
|
+
private createWorkerThread() {
|
|
59
|
+
this.worker = new this.Worker(
|
|
60
|
+
new URL('./workers/inlet/LslStreamInlet.worker.js', import.meta.url)
|
|
99
61
|
)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
private readonly bytesPerFloat = 4
|
|
103
|
-
|
|
104
|
-
private createTimestampBuffer() {
|
|
105
|
-
this.timestampBuffer = Buffer.alloc(
|
|
106
|
-
this.bytesPerDouble * this.chunkSize
|
|
107
|
-
)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
private readonly bytesPerDouble = 8
|
|
111
|
-
|
|
112
|
-
private createErrorCodeBuffer() {
|
|
113
|
-
this.errorCodeBuffer = Buffer.alloc(this.bytesPerInt)
|
|
114
|
-
}
|
|
115
62
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
paramsValue: [this.timestampBuffer],
|
|
138
|
-
})
|
|
139
|
-
)[0]
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
private createErrorCodeBufferPtr() {
|
|
143
|
-
this.errorCodeBufferPtr = unwrapPointer(
|
|
144
|
-
createPointer({
|
|
145
|
-
paramsType: [DataType.U8Array],
|
|
146
|
-
paramsValue: [this.errorCodeBuffer],
|
|
147
|
-
})
|
|
148
|
-
)[0]
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
private async pullOnLoop() {
|
|
152
|
-
if (this.isRunning) {
|
|
153
|
-
const { samples, timestamps } = this.pullDataMethod()
|
|
154
|
-
this.handleErrorCodeIfPresent()
|
|
155
|
-
|
|
156
|
-
if (samples && timestamps) {
|
|
157
|
-
this.onData(samples, timestamps)
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
setImmediate(() => {
|
|
161
|
-
void this.pullOnLoop()
|
|
162
|
-
})
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
private pullSample() {
|
|
167
|
-
const timestamp = this.callPullSampleBinding()
|
|
168
|
-
|
|
169
|
-
if (timestamp) {
|
|
170
|
-
return {
|
|
171
|
-
samples: this.createFloatArrayFromDataBuffer(),
|
|
172
|
-
timestamps: new Float64Array([timestamp]),
|
|
63
|
+
this.worker.on('message', (msg) => {
|
|
64
|
+
const { type } = msg
|
|
65
|
+
|
|
66
|
+
switch (type) {
|
|
67
|
+
case 'ready': {
|
|
68
|
+
this.workerReady = true
|
|
69
|
+
break
|
|
70
|
+
}
|
|
71
|
+
case 'data': {
|
|
72
|
+
const { payload } = msg
|
|
73
|
+
const { samples, timestamps } = payload
|
|
74
|
+
|
|
75
|
+
this.onData(samples, timestamps)
|
|
76
|
+
break
|
|
77
|
+
}
|
|
78
|
+
case 'error': {
|
|
79
|
+
const { error } = msg
|
|
80
|
+
throw new Error(error)
|
|
81
|
+
}
|
|
82
|
+
default:
|
|
83
|
+
throw new Error(`Unknown message type: ${type}`)
|
|
173
84
|
}
|
|
174
|
-
}
|
|
175
|
-
return { samples: undefined, timestamps: undefined }
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
private callPullSampleBinding() {
|
|
179
|
-
return this.lsl.pullSample({
|
|
180
|
-
inlet: this.inlet,
|
|
181
|
-
dataBufferPtr: this.dataBufferPtr,
|
|
182
|
-
dataBufferElements: this.channelCount,
|
|
183
|
-
timeout: this.timeoutMs / 1000,
|
|
184
|
-
errcodePtr: this.errorCodeBufferPtr,
|
|
185
85
|
})
|
|
186
86
|
}
|
|
187
87
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
this.
|
|
191
|
-
this.
|
|
192
|
-
this.chunkSize * this.channelCount
|
|
193
|
-
)
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
private pullChunk() {
|
|
197
|
-
const firstTimestamp = this.callPullChunkBinding()
|
|
88
|
+
public async startPulling() {
|
|
89
|
+
if (!this.isRunning) {
|
|
90
|
+
this.isRunning = true
|
|
91
|
+
this.postCreate()
|
|
198
92
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
samples: this.createFloatArrayFromDataBuffer(),
|
|
202
|
-
timestamps: this.createDoubleArrayFromTimestampBuffer(),
|
|
93
|
+
while (!this.workerReady) {
|
|
94
|
+
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
203
95
|
}
|
|
96
|
+
|
|
97
|
+
this.postStartPulling()
|
|
98
|
+
} else {
|
|
99
|
+
console.warn('Skipping startPulling: inlet is already running!')
|
|
204
100
|
}
|
|
205
|
-
return { samples: undefined, timestamps: undefined }
|
|
206
101
|
}
|
|
207
102
|
|
|
208
|
-
private
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
103
|
+
private postCreate() {
|
|
104
|
+
this.worker.postMessage({
|
|
105
|
+
type: 'createInlet',
|
|
106
|
+
payload: {
|
|
107
|
+
sourceId: this.sourceId,
|
|
108
|
+
chunkSize: this.chunkSize,
|
|
109
|
+
maxBufferedMs: this.maxBufferedMs,
|
|
110
|
+
openStreamTimeoutMs: this.openStreamTimeoutMs,
|
|
111
|
+
waitAfterOpenStreamMs: this.waitAfterOpenStreamMs,
|
|
112
|
+
pullTimeoutMs: this.pullTimeoutMs,
|
|
113
|
+
waitBetweenPullsMs: this.waitBetweenPullsMs,
|
|
114
|
+
flushInletOnStop: this.flushInletOnStop,
|
|
115
|
+
},
|
|
217
116
|
})
|
|
218
117
|
}
|
|
219
118
|
|
|
220
|
-
private
|
|
221
|
-
|
|
222
|
-
this.timestampBuffer.buffer,
|
|
223
|
-
this.timestampBuffer.byteOffset,
|
|
224
|
-
this.chunkSize
|
|
225
|
-
)
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
private handleErrorCodeIfPresent() {
|
|
229
|
-
handleError(this.errorCodeBuffer.readInt32LE())
|
|
119
|
+
private postStartPulling() {
|
|
120
|
+
this.worker.postMessage({ type: 'startPulling' })
|
|
230
121
|
}
|
|
231
122
|
|
|
232
123
|
public stopPulling() {
|
|
233
124
|
this.isRunning = false
|
|
125
|
+
this.worker.postMessage({ type: 'stopPulling' })
|
|
234
126
|
}
|
|
235
127
|
|
|
236
|
-
public
|
|
237
|
-
this.
|
|
128
|
+
public flushInlet() {
|
|
129
|
+
this.worker.postMessage({ type: 'flushInlet' })
|
|
238
130
|
}
|
|
239
131
|
|
|
240
132
|
public destroy() {
|
|
241
|
-
this.
|
|
133
|
+
if (this.isRunning) {
|
|
134
|
+
this.stopPulling()
|
|
135
|
+
}
|
|
136
|
+
this.worker.postMessage({ type: 'destroyInlet' })
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private get defaultWaitMs() {
|
|
140
|
+
return LslStreamInlet.waitAfterOpenStreamMs
|
|
242
141
|
}
|
|
243
142
|
|
|
244
|
-
private
|
|
245
|
-
|
|
143
|
+
private get Worker() {
|
|
144
|
+
return LslStreamInlet.Worker
|
|
246
145
|
}
|
|
247
146
|
}
|
|
248
147
|
|
|
249
148
|
export interface StreamInlet {
|
|
250
|
-
startPulling(): void
|
|
149
|
+
startPulling(): Promise<void>
|
|
251
150
|
stopPulling(): void
|
|
252
|
-
|
|
151
|
+
flushInlet(): void
|
|
253
152
|
destroy(): void
|
|
254
153
|
readonly isRunning: boolean
|
|
255
154
|
}
|
|
256
155
|
|
|
257
156
|
export type StreamInletConstructor = new (
|
|
258
|
-
|
|
259
|
-
options: StreamInletConstructorOptions,
|
|
157
|
+
options: StreamInletOptions,
|
|
260
158
|
onData: OnDataCallback
|
|
261
159
|
) => StreamInlet
|
|
262
160
|
|
|
263
161
|
export interface StreamInletOptions {
|
|
264
|
-
|
|
162
|
+
sourceId: string
|
|
265
163
|
chunkSize: number
|
|
266
164
|
maxBufferedMs?: number
|
|
267
|
-
|
|
165
|
+
openStreamTimeoutMs?: number
|
|
166
|
+
waitAfterOpenStreamMs?: number
|
|
167
|
+
pullTimeoutMs?: number
|
|
168
|
+
waitBetweenPullsMs?: number
|
|
169
|
+
flushInletOnStop?: boolean
|
|
268
170
|
}
|
|
269
171
|
|
|
270
|
-
export type
|
|
271
|
-
|
|
272
|
-
export type OnDataCallback = (
|
|
273
|
-
samples: Float32Array,
|
|
274
|
-
timestamps: Float64Array
|
|
275
|
-
) => void
|
|
172
|
+
export type OnDataCallback = (samples: number[], timestamps: number[]) => void
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { Worker } from 'node:worker_threads'
|
|
2
|
+
|
|
3
|
+
import { ChannelFormat, Liblsl, LslSample } from '@neurodevs/ndx-native'
|
|
4
|
+
|
|
1
5
|
import {
|
|
2
6
|
assertValidChannelCount,
|
|
3
7
|
assertValidChannelFormat,
|
|
@@ -5,23 +9,11 @@ import {
|
|
|
5
9
|
assertValidMaxBufferedMs,
|
|
6
10
|
assertValidSampleRateHz,
|
|
7
11
|
} from '../assertions.js'
|
|
8
|
-
import handleError, { LslErrorCode } from '../handleError.js'
|
|
9
|
-
import {
|
|
10
|
-
ChannelFormat,
|
|
11
|
-
BoundOutlet,
|
|
12
|
-
Liblsl,
|
|
13
|
-
LslSample,
|
|
14
|
-
} from './LiblslAdapter.js'
|
|
15
|
-
import LiblslAdapter from './LiblslAdapter.js'
|
|
16
|
-
import LslStreamInfo, {
|
|
17
|
-
StreamInfo,
|
|
18
|
-
StreamInfoOptions,
|
|
19
|
-
} from './LslStreamInfo.js'
|
|
20
12
|
|
|
21
13
|
export default class LslStreamOutlet implements StreamOutlet {
|
|
22
14
|
public static Class?: StreamOutletConstructor
|
|
15
|
+
public static Worker = Worker
|
|
23
16
|
|
|
24
|
-
public readonly info: StreamInfo
|
|
25
17
|
public readonly name: string
|
|
26
18
|
public readonly type: string
|
|
27
19
|
public readonly sourceId: string
|
|
@@ -34,12 +26,9 @@ export default class LslStreamOutlet implements StreamOutlet {
|
|
|
34
26
|
public readonly manufacturer: string = 'N/A'
|
|
35
27
|
public readonly units: string = 'N/A'
|
|
36
28
|
|
|
37
|
-
private
|
|
38
|
-
private pushSampleMethod!: (options: unknown) => LslErrorCode
|
|
29
|
+
private worker!: Worker
|
|
39
30
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
protected constructor(info: StreamInfo, options: StreamOutletOptions) {
|
|
31
|
+
protected constructor(options: StreamOutletOptions) {
|
|
43
32
|
const {
|
|
44
33
|
name,
|
|
45
34
|
type,
|
|
@@ -53,7 +42,6 @@ export default class LslStreamOutlet implements StreamOutlet {
|
|
|
53
42
|
units,
|
|
54
43
|
} = options
|
|
55
44
|
|
|
56
|
-
this.info = info
|
|
57
45
|
this.name = name
|
|
58
46
|
this.type = type
|
|
59
47
|
this.sourceId = sourceId
|
|
@@ -67,17 +55,15 @@ export default class LslStreamOutlet implements StreamOutlet {
|
|
|
67
55
|
this.units = units ?? this.units
|
|
68
56
|
|
|
69
57
|
this.validateOptions()
|
|
58
|
+
this.createWorkerThread()
|
|
70
59
|
this.createStreamOutlet()
|
|
71
|
-
this.setPushSampleMethod()
|
|
72
60
|
}
|
|
73
61
|
|
|
74
62
|
public static async Create(options: StreamOutletOptions) {
|
|
75
63
|
const { waitAfterConstructionMs = 10 } = options ?? {}
|
|
76
64
|
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
await this.waitToAllowSetup(waitAfterConstructionMs)
|
|
65
|
+
const instance = new (this.Class ?? this)(options)
|
|
66
|
+
await this.waitForSetup(waitAfterConstructionMs)
|
|
81
67
|
|
|
82
68
|
return instance
|
|
83
69
|
}
|
|
@@ -107,18 +93,33 @@ export default class LslStreamOutlet implements StreamOutlet {
|
|
|
107
93
|
)
|
|
108
94
|
}
|
|
109
95
|
|
|
110
|
-
private
|
|
111
|
-
this.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
96
|
+
private createWorkerThread() {
|
|
97
|
+
this.worker = new this.Worker(
|
|
98
|
+
new URL(
|
|
99
|
+
'./workers/outlet/LslStreamOutlet.worker.js',
|
|
100
|
+
import.meta.url
|
|
101
|
+
)
|
|
102
|
+
)
|
|
116
103
|
}
|
|
117
104
|
|
|
118
|
-
private
|
|
119
|
-
this.
|
|
120
|
-
|
|
121
|
-
|
|
105
|
+
private createStreamOutlet() {
|
|
106
|
+
this.worker.postMessage({
|
|
107
|
+
type: 'createOutlet',
|
|
108
|
+
payload: {
|
|
109
|
+
infoOptions: {
|
|
110
|
+
name: this.name,
|
|
111
|
+
type: this.type,
|
|
112
|
+
sourceId: this.sourceId,
|
|
113
|
+
channelNames: this.channelNames,
|
|
114
|
+
channelFormat: this.channelFormat,
|
|
115
|
+
sampleRateHz: this.sampleRateHz,
|
|
116
|
+
units: this.units,
|
|
117
|
+
},
|
|
118
|
+
chunkSize: this.chunkSize,
|
|
119
|
+
maxBufferedMs: this.maxBufferedMs,
|
|
120
|
+
pushMethod: this.pushMethod,
|
|
121
|
+
},
|
|
122
|
+
})
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
private get pushMethod() {
|
|
@@ -130,63 +131,36 @@ export default class LslStreamOutlet implements StreamOutlet {
|
|
|
130
131
|
string: 'pushSampleStringTimestamp',
|
|
131
132
|
}
|
|
132
133
|
|
|
133
|
-
public pushSample(sample: LslSample) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
134
|
+
public pushSample(sample: LslSample, timestamp?: number) {
|
|
135
|
+
this.worker.postMessage({
|
|
136
|
+
type: 'pushSample',
|
|
137
|
+
payload: {
|
|
138
|
+
sample,
|
|
139
|
+
timestamp,
|
|
140
|
+
},
|
|
140
141
|
})
|
|
141
|
-
handleError(err)
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
public destroy() {
|
|
145
|
-
this.
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
private destroyBoundInfo() {
|
|
150
|
-
this.lsl.destroyStreamInfo({ info: this.info.boundInfo })
|
|
145
|
+
this.worker.postMessage({
|
|
146
|
+
type: 'destroyOutlet',
|
|
147
|
+
})
|
|
151
148
|
}
|
|
152
149
|
|
|
153
|
-
private
|
|
154
|
-
|
|
150
|
+
private get Worker() {
|
|
151
|
+
return LslStreamOutlet.Worker
|
|
155
152
|
}
|
|
156
153
|
|
|
157
|
-
private static async
|
|
154
|
+
private static async waitForSetup(waitAfterConstructionMs: number) {
|
|
158
155
|
await new Promise((resolve) =>
|
|
159
156
|
setTimeout(resolve, waitAfterConstructionMs)
|
|
160
157
|
)
|
|
161
158
|
}
|
|
162
|
-
|
|
163
|
-
private static LslStreamInfo(options: StreamInfoOptions) {
|
|
164
|
-
const {
|
|
165
|
-
channelNames,
|
|
166
|
-
channelFormat,
|
|
167
|
-
sampleRateHz,
|
|
168
|
-
name,
|
|
169
|
-
type,
|
|
170
|
-
sourceId,
|
|
171
|
-
units,
|
|
172
|
-
} = options
|
|
173
|
-
|
|
174
|
-
return LslStreamInfo.Create({
|
|
175
|
-
name,
|
|
176
|
-
type,
|
|
177
|
-
sourceId,
|
|
178
|
-
channelNames,
|
|
179
|
-
channelFormat,
|
|
180
|
-
sampleRateHz,
|
|
181
|
-
units,
|
|
182
|
-
})
|
|
183
|
-
}
|
|
184
159
|
}
|
|
185
160
|
|
|
186
161
|
export interface StreamOutlet {
|
|
187
|
-
pushSample(sample: LslSample): void
|
|
162
|
+
pushSample(sample: LslSample, timestamp?: number): void
|
|
188
163
|
destroy(): void
|
|
189
|
-
readonly info: StreamInfo
|
|
190
164
|
readonly name: string
|
|
191
165
|
readonly type: string
|
|
192
166
|
readonly sourceId: string
|
|
@@ -201,11 +175,14 @@ export interface StreamOutlet {
|
|
|
201
175
|
}
|
|
202
176
|
|
|
203
177
|
export type StreamOutletConstructor = new (
|
|
204
|
-
info: StreamInfo,
|
|
205
178
|
options: StreamOutletOptions
|
|
206
179
|
) => StreamOutlet
|
|
207
180
|
|
|
208
|
-
export interface StreamOutletOptions {
|
|
181
|
+
export interface StreamOutletOptions extends StreamOutletConstructorOptions {
|
|
182
|
+
waitAfterConstructionMs?: number
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export interface StreamOutletConstructorOptions {
|
|
209
186
|
name: string
|
|
210
187
|
type: string
|
|
211
188
|
sourceId: string
|
|
@@ -216,5 +193,4 @@ export interface StreamOutletOptions {
|
|
|
216
193
|
maxBufferedMs?: number
|
|
217
194
|
manufacturer?: string
|
|
218
195
|
units?: string
|
|
219
|
-
waitAfterConstructionMs?: number
|
|
220
196
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import WebSocket, { WebSocketServer } from 'ws'
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import LslStreamInlet, {
|
|
4
4
|
StreamInlet,
|
|
5
5
|
StreamInletOptions,
|
|
@@ -55,9 +55,9 @@ export default class LslWebSocketBridge implements WebSocketBridge {
|
|
|
55
55
|
|
|
56
56
|
private readonly insufficientOptionsError = `At least one of listenPort or connectUrls must be provided!`
|
|
57
57
|
|
|
58
|
-
public activate() {
|
|
58
|
+
public async activate() {
|
|
59
59
|
this.throwIfBridgeIsDestroyed(this.cannotActivateMessage)
|
|
60
|
-
this.startPullingData()
|
|
60
|
+
await this.startPullingData()
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
private throwIfBridgeIsDestroyed(err: string) {
|
|
@@ -68,8 +68,8 @@ export default class LslWebSocketBridge implements WebSocketBridge {
|
|
|
68
68
|
|
|
69
69
|
private readonly cannotActivateMessage = `\n\n Cannot activate bridge after destroying it! \n\n Please create and activate a new instance. \n`
|
|
70
70
|
|
|
71
|
-
private startPullingData() {
|
|
72
|
-
this.inlet.startPulling()
|
|
71
|
+
private async startPullingData() {
|
|
72
|
+
await this.inlet.startPulling()
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
public deactivate() {
|
|
@@ -84,13 +84,13 @@ export default class LslWebSocketBridge implements WebSocketBridge {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
public destroy() {
|
|
87
|
-
this.destroyBoundInlet()
|
|
88
87
|
this.closeLocalServerIfExists()
|
|
89
88
|
this.closeRemoteSocketsIfExists()
|
|
89
|
+
this.destroyInlet()
|
|
90
90
|
this.isDestroyed = true
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
private
|
|
93
|
+
private destroyInlet() {
|
|
94
94
|
this.inlet.destroy()
|
|
95
95
|
}
|
|
96
96
|
|
|
@@ -106,14 +106,14 @@ export default class LslWebSocketBridge implements WebSocketBridge {
|
|
|
106
106
|
localServer?: WebSocketServer,
|
|
107
107
|
remoteSockets?: WebSocket[]
|
|
108
108
|
) {
|
|
109
|
-
return (samples:
|
|
109
|
+
return (samples: number[], timestamps: number[]) => {
|
|
110
110
|
const payload = JSON.stringify({ samples, timestamps })
|
|
111
111
|
|
|
112
|
-
if (localServer) {
|
|
112
|
+
if (localServer && localServer.clients.size > 0) {
|
|
113
113
|
this.broadcastToClients(localServer, payload)
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
if (remoteSockets) {
|
|
116
|
+
if (remoteSockets && remoteSockets.length > 0) {
|
|
117
117
|
this.sendToSockets(remoteSockets, payload)
|
|
118
118
|
}
|
|
119
119
|
}
|
|
@@ -165,7 +165,7 @@ export default class LslWebSocketBridge implements WebSocketBridge {
|
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
export interface WebSocketBridge {
|
|
168
|
-
activate(): void
|
|
168
|
+
activate(): Promise<void>
|
|
169
169
|
deactivate(): void
|
|
170
170
|
destroy(): void
|
|
171
171
|
}
|
|
@@ -175,7 +175,7 @@ export type WebSocketBridgeConstructor = new (
|
|
|
175
175
|
) => WebSocketBridge
|
|
176
176
|
|
|
177
177
|
export interface WebSocketBridgeOptions {
|
|
178
|
-
|
|
178
|
+
sourceId: string
|
|
179
179
|
chunkSize: number
|
|
180
180
|
listenPort?: number
|
|
181
181
|
connectUrls?: string | string[]
|