@neurodevs/node-lsl 17.1.1 → 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 +20 -8
- package/build/__tests__/impl/LslStreamInlet.test.js +224 -69
- 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 -50
- package/build/impl/LslStreamInlet.js +79 -149
- 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 +23 -7
- package/src/.spruce/settings.json +5 -1
- package/src/__tests__/AbstractPackageTest.ts +12 -14
- 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 +344 -97
- 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 +102 -215
- 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 -154
- 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 -543
- 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,285 +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 wrappedDataBufferPtr!: JsExternal[]
|
|
29
|
-
private dataBufferPtr!: JsExternal
|
|
30
|
-
|
|
31
|
-
private timestampBuffer!: Buffer<ArrayBuffer>
|
|
32
|
-
private wrappedTimestampBufferPtr!: JsExternal[]
|
|
33
|
-
private timestampBufferPtr!: JsExternal
|
|
34
|
-
|
|
35
|
-
private errorCodeBuffer!: Buffer<ArrayBuffer>
|
|
36
|
-
private wrappedErrorCodeBufferPtr!: JsExternal[]
|
|
37
|
-
private errorCodeBufferPtr!: JsExternal
|
|
18
|
+
private worker!: Worker
|
|
19
|
+
private workerReady = false
|
|
38
20
|
|
|
39
21
|
private readonly sixMinutesInMs = 360 * 1000
|
|
22
|
+
private readonly aboutOneYearInMs = 32000000 * 1000
|
|
40
23
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
protected constructor(
|
|
44
|
-
info: StreamInfo,
|
|
45
|
-
options: StreamInletConstructorOptions,
|
|
46
|
-
onData: OnDataCallback
|
|
47
|
-
) {
|
|
48
|
-
const { chunkSize, maxBufferedMs, timeoutMs } = options ?? {}
|
|
24
|
+
public isRunning = false
|
|
49
25
|
|
|
50
|
-
|
|
51
|
-
|
|
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
|
|
52
39
|
this.chunkSize = chunkSize
|
|
53
40
|
this.maxBufferedMs = maxBufferedMs ?? this.sixMinutesInMs
|
|
54
|
-
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
|
|
55
46
|
this.onData = onData
|
|
56
47
|
|
|
57
|
-
this.
|
|
58
|
-
this.createBoundInlet()
|
|
48
|
+
this.createWorkerThread()
|
|
59
49
|
}
|
|
60
50
|
|
|
61
51
|
public static async Create(
|
|
62
52
|
options: StreamInletOptions,
|
|
63
53
|
onData: OnDataCallback
|
|
64
54
|
) {
|
|
65
|
-
|
|
66
|
-
return new (this.Class ?? this)(info, options, onData)
|
|
55
|
+
return new (this.Class ?? this)(options, onData)
|
|
67
56
|
}
|
|
68
57
|
|
|
69
|
-
private
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
} else {
|
|
73
|
-
this.pullDataMethod = this.pullChunk
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
private createBoundInlet() {
|
|
78
|
-
this.inlet = this.lsl.createInlet({
|
|
79
|
-
info: this.info.boundInfo,
|
|
80
|
-
maxBufferedMs: this.maxBufferedMs,
|
|
81
|
-
})
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
public startPulling() {
|
|
85
|
-
this.isRunning = true
|
|
86
|
-
|
|
87
|
-
this.createWriteableBuffers()
|
|
88
|
-
this.createPointersToBuffers()
|
|
89
|
-
|
|
90
|
-
void this.pullOnLoop()
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
private createWriteableBuffers() {
|
|
94
|
-
this.createDataBuffer()
|
|
95
|
-
this.createTimestampBuffer()
|
|
96
|
-
this.createErrorCodeBuffer()
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
private createDataBuffer() {
|
|
100
|
-
this.dataBuffer = Buffer.alloc(
|
|
101
|
-
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)
|
|
102
61
|
)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
private readonly bytesPerFloat = 4
|
|
106
|
-
|
|
107
|
-
private createTimestampBuffer() {
|
|
108
|
-
this.timestampBuffer = Buffer.alloc(
|
|
109
|
-
this.bytesPerDouble * this.chunkSize
|
|
110
|
-
)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
private readonly bytesPerDouble = 8
|
|
114
|
-
|
|
115
|
-
private createErrorCodeBuffer() {
|
|
116
|
-
this.errorCodeBuffer = Buffer.alloc(this.bytesPerInt)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
private readonly bytesPerInt = 4
|
|
120
|
-
|
|
121
|
-
private createPointersToBuffers() {
|
|
122
|
-
this.createDataBufferPtr()
|
|
123
|
-
this.createTimestampBufferPtr()
|
|
124
|
-
this.createErrorCodeBufferPtr()
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
private createDataBufferPtr() {
|
|
128
|
-
this.wrappedDataBufferPtr = createPointer({
|
|
129
|
-
paramsType: [DataType.U8Array],
|
|
130
|
-
paramsValue: [this.dataBuffer],
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
this.dataBufferPtr = unwrapPointer(this.wrappedDataBufferPtr)[0]
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
private createTimestampBufferPtr() {
|
|
137
|
-
this.wrappedTimestampBufferPtr = createPointer({
|
|
138
|
-
paramsType: [DataType.U8Array],
|
|
139
|
-
paramsValue: [this.timestampBuffer],
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
this.timestampBufferPtr = unwrapPointer(
|
|
143
|
-
this.wrappedTimestampBufferPtr
|
|
144
|
-
)[0]
|
|
145
|
-
}
|
|
146
62
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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}`)
|
|
165
84
|
}
|
|
166
|
-
|
|
167
|
-
setImmediate(() => {
|
|
168
|
-
void this.pullOnLoop()
|
|
169
|
-
})
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
private pullSample() {
|
|
174
|
-
const timestamp = this.callPullSampleBinding()
|
|
175
|
-
|
|
176
|
-
if (timestamp > 0) {
|
|
177
|
-
return {
|
|
178
|
-
samples: this.createFloatArrayFromDataBuffer(),
|
|
179
|
-
timestamps: new Float64Array([timestamp]),
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
return { samples: undefined, timestamps: undefined }
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
private callPullSampleBinding() {
|
|
186
|
-
return this.lsl.pullSample({
|
|
187
|
-
inlet: this.inlet,
|
|
188
|
-
dataBufferPtr: this.dataBufferPtr,
|
|
189
|
-
dataBufferElements: this.channelCount,
|
|
190
|
-
timeout: this.timeoutMs / 1000,
|
|
191
|
-
errcodePtr: this.errorCodeBufferPtr,
|
|
192
85
|
})
|
|
193
86
|
}
|
|
194
87
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
this.
|
|
198
|
-
this.
|
|
199
|
-
this.chunkSize * this.channelCount
|
|
200
|
-
)
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
private pullChunk() {
|
|
204
|
-
const firstTimestamp = this.callPullChunkBinding()
|
|
88
|
+
public async startPulling() {
|
|
89
|
+
if (!this.isRunning) {
|
|
90
|
+
this.isRunning = true
|
|
91
|
+
this.postCreate()
|
|
205
92
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
samples: this.createFloatArrayFromDataBuffer(),
|
|
209
|
-
timestamps: this.createDoubleArrayFromTimestampBuffer(),
|
|
93
|
+
while (!this.workerReady) {
|
|
94
|
+
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
210
95
|
}
|
|
96
|
+
|
|
97
|
+
this.postStartPulling()
|
|
98
|
+
} else {
|
|
99
|
+
console.warn('Skipping startPulling: inlet is already running!')
|
|
211
100
|
}
|
|
212
|
-
return { samples: undefined, timestamps: undefined }
|
|
213
101
|
}
|
|
214
102
|
|
|
215
|
-
private
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
+
},
|
|
224
116
|
})
|
|
225
117
|
}
|
|
226
118
|
|
|
227
|
-
private
|
|
228
|
-
|
|
229
|
-
this.timestampBuffer.buffer,
|
|
230
|
-
this.timestampBuffer.byteOffset,
|
|
231
|
-
this.chunkSize
|
|
232
|
-
)
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
private handleErrorCodeIfPresent() {
|
|
236
|
-
handleError(this.errorCodeBuffer.readInt32LE())
|
|
119
|
+
private postStartPulling() {
|
|
120
|
+
this.worker.postMessage({ type: 'startPulling' })
|
|
237
121
|
}
|
|
238
122
|
|
|
239
123
|
public stopPulling() {
|
|
240
124
|
this.isRunning = false
|
|
125
|
+
this.worker.postMessage({ type: 'stopPulling' })
|
|
241
126
|
}
|
|
242
127
|
|
|
243
|
-
public
|
|
244
|
-
this.
|
|
128
|
+
public flushInlet() {
|
|
129
|
+
this.worker.postMessage({ type: 'flushInlet' })
|
|
245
130
|
}
|
|
246
131
|
|
|
247
132
|
public destroy() {
|
|
248
133
|
if (this.isRunning) {
|
|
249
134
|
this.stopPulling()
|
|
250
135
|
}
|
|
251
|
-
this.
|
|
136
|
+
this.worker.postMessage({ type: 'destroyInlet' })
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private get defaultWaitMs() {
|
|
140
|
+
return LslStreamInlet.waitAfterOpenStreamMs
|
|
252
141
|
}
|
|
253
142
|
|
|
254
|
-
private
|
|
255
|
-
|
|
143
|
+
private get Worker() {
|
|
144
|
+
return LslStreamInlet.Worker
|
|
256
145
|
}
|
|
257
146
|
}
|
|
258
147
|
|
|
259
148
|
export interface StreamInlet {
|
|
260
|
-
startPulling(): void
|
|
149
|
+
startPulling(): Promise<void>
|
|
261
150
|
stopPulling(): void
|
|
262
|
-
|
|
151
|
+
flushInlet(): void
|
|
263
152
|
destroy(): void
|
|
264
153
|
readonly isRunning: boolean
|
|
265
154
|
}
|
|
266
155
|
|
|
267
156
|
export type StreamInletConstructor = new (
|
|
268
|
-
|
|
269
|
-
options: StreamInletConstructorOptions,
|
|
157
|
+
options: StreamInletOptions,
|
|
270
158
|
onData: OnDataCallback
|
|
271
159
|
) => StreamInlet
|
|
272
160
|
|
|
273
161
|
export interface StreamInletOptions {
|
|
274
|
-
|
|
162
|
+
sourceId: string
|
|
275
163
|
chunkSize: number
|
|
276
164
|
maxBufferedMs?: number
|
|
277
|
-
|
|
165
|
+
openStreamTimeoutMs?: number
|
|
166
|
+
waitAfterOpenStreamMs?: number
|
|
167
|
+
pullTimeoutMs?: number
|
|
168
|
+
waitBetweenPullsMs?: number
|
|
169
|
+
flushInletOnStop?: boolean
|
|
278
170
|
}
|
|
279
171
|
|
|
280
|
-
export type
|
|
281
|
-
|
|
282
|
-
export type OnDataCallback = (
|
|
283
|
-
samples: Float32Array,
|
|
284
|
-
timestamps: Float64Array
|
|
285
|
-
) => 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[]
|