@neurodevs/node-lsl 18.1.0 → 20.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/build/__tests__/AbstractPackageTest.d.ts +19 -17
- package/build/__tests__/AbstractPackageTest.js +54 -39
- package/build/__tests__/AbstractPackageTest.js.map +1 -1
- package/build/__tests__/impl/BleDeviceController.test.d.ts +6 -1
- package/build/__tests__/impl/BleDeviceController.test.js +44 -15
- package/build/__tests__/impl/BleDeviceController.test.js.map +1 -1
- package/build/__tests__/impl/LslEventMarkerEmitter.test.js +13 -13
- package/build/__tests__/impl/LslEventMarkerEmitter.test.js.map +1 -1
- package/build/__tests__/impl/LslStreamInfo.test.js +2 -2
- package/build/__tests__/impl/LslStreamInfo.test.js.map +1 -1
- package/build/__tests__/impl/LslStreamInlet.test.d.ts +3 -5
- package/build/__tests__/impl/LslStreamInlet.test.js +13 -46
- package/build/__tests__/impl/LslStreamInlet.test.js.map +1 -1
- package/build/__tests__/impl/LslStreamOutlet.test.js +9 -16
- package/build/__tests__/impl/LslStreamOutlet.test.js.map +1 -1
- package/build/__tests__/impl/LslWebSocketBridge.test.js +9 -10
- package/build/__tests__/impl/LslWebSocketBridge.test.js.map +1 -1
- package/build/__tests__/impl/UsbDeviceController.test.d.ts +17 -0
- package/build/__tests__/impl/UsbDeviceController.test.js +81 -0
- package/build/__tests__/impl/UsbDeviceController.test.js.map +1 -0
- package/build/__tests__/impl/WindowedClockRegressor.test.d.ts +19 -0
- package/build/__tests__/impl/WindowedClockRegressor.test.js +172 -0
- package/build/__tests__/impl/WindowedClockRegressor.test.js.map +1 -0
- package/build/assertions.js +1 -1
- package/build/assertions.js.map +1 -1
- package/build/impl/LslEventMarkerEmitter.d.ts +12 -12
- package/build/impl/LslEventMarkerEmitter.js.map +1 -1
- package/build/impl/LslStreamInfo.d.ts +7 -7
- package/build/impl/LslStreamInfo.js.map +1 -1
- package/build/impl/LslStreamInlet.d.ts +63 -20
- package/build/impl/LslStreamInlet.js +221 -64
- package/build/impl/LslStreamInlet.js.map +1 -1
- package/build/impl/LslStreamOutlet.d.ts +16 -14
- package/build/impl/LslStreamOutlet.js +34 -37
- package/build/impl/LslStreamOutlet.js.map +1 -1
- package/build/impl/LslWebSocketBridge.d.ts +10 -10
- package/build/impl/LslWebSocketBridge.js.map +1 -1
- package/build/impl/clocks/WindowedClockRegressor.d.ts +24 -0
- package/build/impl/clocks/WindowedClockRegressor.js +73 -0
- package/build/impl/clocks/WindowedClockRegressor.js.map +1 -0
- package/build/impl/controllers/BleDeviceController.d.ts +3 -1
- package/build/impl/controllers/BleDeviceController.js +9 -1
- package/build/impl/controllers/BleDeviceController.js.map +1 -1
- package/build/impl/controllers/UsbDeviceController.d.ts +23 -0
- package/build/impl/controllers/UsbDeviceController.js +40 -0
- package/build/impl/controllers/UsbDeviceController.js.map +1 -0
- package/build/index.d.ts +32 -20
- package/build/index.js +39 -26
- package/build/index.js.map +1 -1
- package/build/scripts/{runLslEventMarkerEmitter.js → runLslEmitter.js} +5 -4
- package/build/scripts/runLslEmitter.js.map +1 -0
- package/build/scripts/{runLslStreamOutlet.js → runLslOutlet.js} +1 -1
- package/build/scripts/runLslOutlet.js.map +1 -0
- package/build/scripts/runMuse.js +1 -1
- package/build/scripts/runMuse.js.map +1 -1
- package/build/scripts/runUsb.js +14 -0
- package/build/scripts/runUsb.js.map +1 -0
- package/build/testDoubles/BleController/FakeBleController.d.ts +3 -1
- package/build/testDoubles/BleController/FakeBleController.js +5 -0
- package/build/testDoubles/BleController/FakeBleController.js.map +1 -1
- package/build/testDoubles/ClockRegressor/FakeClockRegressor.d.ts +17 -0
- package/build/testDoubles/ClockRegressor/FakeClockRegressor.js +23 -0
- package/build/testDoubles/ClockRegressor/FakeClockRegressor.js.map +1 -0
- package/build/testDoubles/{EventMarkerEmitter/FakeEventMarkerEmitter.d.ts → LslEmitter/FakeLslEmitter.d.ts} +4 -4
- package/build/testDoubles/{EventMarkerEmitter/FakeEventMarkerEmitter.js → LslEmitter/FakeLslEmitter.js} +7 -7
- package/build/testDoubles/LslEmitter/FakeLslEmitter.js.map +1 -0
- package/build/testDoubles/{EventMarkerEmitter/SpyEventMarkerEmitter.d.ts → LslEmitter/SpyLslEmitter.d.ts} +4 -4
- package/build/testDoubles/{EventMarkerEmitter/SpyEventMarkerEmitter.js → LslEmitter/SpyLslEmitter.js} +3 -3
- package/build/testDoubles/LslEmitter/SpyLslEmitter.js.map +1 -0
- package/build/testDoubles/LslEmitter/ThrowingLslEmitter.d.ts +9 -0
- package/build/testDoubles/{EventMarkerEmitter/ThrowingEventMarkerEmitter.js → LslEmitter/ThrowingLslEmitter.js} +2 -2
- package/build/testDoubles/LslEmitter/ThrowingLslEmitter.js.map +1 -0
- package/build/testDoubles/{StreamInfo/FakeStreamInfo.d.ts → LslInfo/FakeLslInfo.d.ts} +4 -4
- package/build/testDoubles/{StreamInfo/FakeStreamInfo.js → LslInfo/FakeLslInfo.js} +7 -7
- package/build/testDoubles/LslInfo/FakeLslInfo.js.map +1 -0
- package/build/testDoubles/LslInfo/SpyLslInfo.d.ts +10 -0
- package/build/testDoubles/{StreamInfo/SpyStreamInfo.js → LslInfo/SpyLslInfo.js} +4 -4
- package/build/testDoubles/LslInfo/SpyLslInfo.js.map +1 -0
- package/build/testDoubles/{StreamInlet/FakeStreamInlet.d.ts → LslInlet/FakeLslInlet.d.ts} +4 -4
- package/build/testDoubles/{StreamInlet/FakeStreamInlet.js → LslInlet/FakeLslInlet.js} +12 -11
- package/build/testDoubles/LslInlet/FakeLslInlet.js.map +1 -0
- package/build/testDoubles/LslInlet/SpyLslInlet.d.ts +4 -0
- package/build/testDoubles/{StreamInlet/SpyStreamInlet.js → LslInlet/SpyLslInlet.js} +2 -2
- package/build/testDoubles/LslInlet/SpyLslInlet.js.map +1 -0
- package/build/testDoubles/{StreamOutlet/FakeStreamOutlet.d.ts → LslOutlet/FakeLslOutlet.d.ts} +5 -5
- package/build/testDoubles/{StreamOutlet/FakeStreamOutlet.js → LslOutlet/FakeLslOutlet.js} +8 -8
- package/build/testDoubles/LslOutlet/FakeLslOutlet.js.map +1 -0
- package/build/testDoubles/LslWsBridge/FakeLslWsBridge.d.ts +14 -0
- package/build/testDoubles/{WebSocketBridge/FakeWebSocketBridge.js → LslWsBridge/FakeLslWsBridge.js} +14 -14
- package/build/testDoubles/LslWsBridge/FakeLslWsBridge.js.map +1 -0
- package/build/testDoubles/LslWsBridge/SpyLslWsBridge.d.ts +4 -0
- package/build/testDoubles/{WebSocketBridge/SpyLslWebSocketBridge.js → LslWsBridge/SpyLslWsBridge.js} +2 -2
- package/build/testDoubles/LslWsBridge/SpyLslWsBridge.js.map +1 -0
- package/build/testDoubles/UsbController/FakeUsbController.d.ts +12 -0
- package/build/testDoubles/UsbController/FakeUsbController.js +25 -0
- package/build/testDoubles/UsbController/FakeUsbController.js.map +1 -0
- package/build/testDoubles/consts.d.ts +2 -12
- package/build/testDoubles/consts.js +0 -20
- package/build/testDoubles/consts.js.map +1 -1
- package/build/testDoubles/generateRandomOutletOptions.d.ts +2 -2
- package/build/testDoubles/generateRandomOutletOptions.js.map +1 -1
- package/package.json +6 -5
- package/src/__tests__/AbstractPackageTest.ts +78 -49
- package/src/__tests__/impl/BleDeviceController.test.ts +59 -18
- package/src/__tests__/impl/LslEventMarkerEmitter.test.ts +19 -24
- package/src/__tests__/impl/LslStreamInfo.test.ts +8 -8
- package/src/__tests__/impl/LslStreamInlet.test.ts +21 -78
- package/src/__tests__/impl/LslStreamOutlet.test.ts +22 -28
- package/src/__tests__/impl/LslWebSocketBridge.test.ts +15 -18
- package/src/__tests__/impl/UsbDeviceController.test.ts +109 -0
- package/src/__tests__/impl/WindowedClockRegressor.test.ts +262 -0
- package/src/assertions.ts +1 -2
- package/src/impl/LslEventMarkerEmitter.ts +15 -17
- package/src/impl/LslStreamInfo.ts +7 -9
- package/src/impl/LslStreamInlet.ts +304 -77
- package/src/impl/LslStreamOutlet.ts +59 -57
- package/src/impl/LslWebSocketBridge.ts +15 -18
- package/src/impl/clocks/WindowedClockRegressor.ts +136 -0
- package/src/impl/controllers/BleDeviceController.ts +18 -1
- package/src/impl/controllers/UsbDeviceController.ts +65 -0
- package/src/index.ts +48 -28
- package/src/scripts/{runLslEventMarkerEmitter.ts → runLslEmitter.ts} +4 -3
- package/src/scripts/runMuse.ts +1 -1
- package/src/scripts/runUsb.ts +20 -0
- package/src/testDoubles/BleController/FakeBleController.ts +11 -0
- package/src/testDoubles/ClockRegressor/FakeClockRegressor.ts +46 -0
- package/src/testDoubles/{EventMarkerEmitter/FakeEventMarkerEmitter.ts → LslEmitter/FakeLslEmitter.ts} +10 -10
- package/src/testDoubles/{EventMarkerEmitter/SpyEventMarkerEmitter.ts → LslEmitter/SpyLslEmitter.ts} +4 -4
- package/src/testDoubles/{EventMarkerEmitter/ThrowingEventMarkerEmitter.ts → LslEmitter/ThrowingLslEmitter.ts} +4 -4
- package/src/testDoubles/{StreamInfo/FakeStreamInfo.ts → LslInfo/FakeLslInfo.ts} +9 -9
- package/src/testDoubles/{StreamInfo/SpyStreamInfo.ts → LslInfo/SpyLslInfo.ts} +5 -5
- package/src/testDoubles/{StreamInlet/FakeStreamInlet.ts → LslInlet/FakeLslInlet.ts} +15 -14
- package/src/testDoubles/LslInlet/SpyLslInlet.ts +10 -0
- package/src/testDoubles/{StreamOutlet/FakeStreamOutlet.ts → LslOutlet/FakeLslOutlet.ts} +11 -14
- package/src/testDoubles/{WebSocketBridge/FakeWebSocketBridge.ts → LslWsBridge/FakeLslWsBridge.ts} +19 -20
- package/src/testDoubles/LslWsBridge/SpyLslWsBridge.ts +9 -0
- package/src/testDoubles/UsbController/FakeUsbController.ts +34 -0
- package/src/testDoubles/consts.ts +3 -23
- package/src/testDoubles/generateRandomOutletOptions.ts +2 -2
- package/build/consts.d.ts +0 -11
- package/build/consts.js +0 -21
- package/build/consts.js.map +0 -1
- package/build/impl/workers/inlet/LslStreamInlet.worker.d.ts +0 -22
- package/build/impl/workers/inlet/LslStreamInlet.worker.js +0 -42
- package/build/impl/workers/inlet/LslStreamInlet.worker.js.map +0 -1
- package/build/impl/workers/inlet/StreamInletWorker.d.ts +0 -73
- package/build/impl/workers/inlet/StreamInletWorker.js +0 -253
- package/build/impl/workers/inlet/StreamInletWorker.js.map +0 -1
- package/build/impl/workers/outlet/LslStreamOutlet.worker.d.ts +0 -32
- package/build/impl/workers/outlet/LslStreamOutlet.worker.js +0 -82
- package/build/impl/workers/outlet/LslStreamOutlet.worker.js.map +0 -1
- package/build/scripts/discoverMuse.js +0 -40
- package/build/scripts/discoverMuse.js.map +0 -1
- package/build/scripts/runLslEventMarkerEmitter.js.map +0 -1
- package/build/scripts/runLslStreamOutlet.js.map +0 -1
- package/build/scripts/testDestroyOutlet.d.ts +0 -1
- package/build/scripts/testDestroyOutlet.js +0 -4
- package/build/scripts/testDestroyOutlet.js.map +0 -1
- package/build/testDoubles/EventMarkerEmitter/FakeEventMarkerEmitter.js.map +0 -1
- package/build/testDoubles/EventMarkerEmitter/SpyEventMarkerEmitter.js.map +0 -1
- package/build/testDoubles/EventMarkerEmitter/ThrowingEventMarkerEmitter.d.ts +0 -9
- package/build/testDoubles/EventMarkerEmitter/ThrowingEventMarkerEmitter.js.map +0 -1
- package/build/testDoubles/StreamInfo/FakeStreamInfo.js.map +0 -1
- package/build/testDoubles/StreamInfo/SpyStreamInfo.d.ts +0 -10
- package/build/testDoubles/StreamInfo/SpyStreamInfo.js.map +0 -1
- package/build/testDoubles/StreamInlet/FakeStreamInlet.js.map +0 -1
- package/build/testDoubles/StreamInlet/SpyStreamInlet.d.ts +0 -4
- package/build/testDoubles/StreamInlet/SpyStreamInlet.js.map +0 -1
- package/build/testDoubles/StreamOutlet/FakeStreamOutlet.js.map +0 -1
- package/build/testDoubles/WebSocketBridge/FakeWebSocketBridge.d.ts +0 -14
- package/build/testDoubles/WebSocketBridge/FakeWebSocketBridge.js.map +0 -1
- package/build/testDoubles/WebSocketBridge/SpyLslWebSocketBridge.d.ts +0 -4
- package/build/testDoubles/WebSocketBridge/SpyLslWebSocketBridge.js.map +0 -1
- package/build/testDoubles/WorkerThreads/FakeWorker.d.ts +0 -27
- package/build/testDoubles/WorkerThreads/FakeWorker.js +0 -67
- package/build/testDoubles/WorkerThreads/FakeWorker.js.map +0 -1
- package/src/consts.ts +0 -21
- package/src/impl/workers/inlet/LslStreamInlet.worker.ts +0 -74
- package/src/impl/workers/inlet/StreamInletWorker.ts +0 -348
- package/src/impl/workers/outlet/LslStreamOutlet.worker.ts +0 -144
- package/src/scripts/discoverMuse.ts +0 -50
- package/src/scripts/testDestroyOutlet.ts +0 -5
- package/src/testDoubles/StreamInlet/SpyStreamInlet.ts +0 -10
- package/src/testDoubles/WebSocketBridge/SpyLslWebSocketBridge.ts +0 -9
- package/src/testDoubles/WorkerThreads/FakeWorker.ts +0 -99
- /package/build/scripts/{discoverMuse.d.ts → runLslEmitter.d.ts} +0 -0
- /package/build/scripts/{runLslEventMarkerEmitter.d.ts → runLslOutlet.d.ts} +0 -0
- /package/build/scripts/{runLslStreamOutlet.d.ts → runUsb.d.ts} +0 -0
- /package/src/scripts/{runLslStreamOutlet.ts → runLslOutlet.ts} +0 -0
|
@@ -1,36 +1,75 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
handleLslError,
|
|
3
|
+
InfoHandle,
|
|
4
|
+
InletHandle,
|
|
5
|
+
LiblslAdapter,
|
|
6
|
+
} from '@neurodevs/ndx-native'
|
|
7
|
+
import {
|
|
8
|
+
JsExternal,
|
|
9
|
+
unwrapPointer,
|
|
10
|
+
createPointer,
|
|
11
|
+
DataType,
|
|
12
|
+
freePointer,
|
|
13
|
+
PointerType,
|
|
14
|
+
} from 'ffi-rs'
|
|
2
15
|
|
|
3
|
-
export default class LslStreamInlet implements
|
|
4
|
-
public static Class?:
|
|
5
|
-
public static Worker = Worker
|
|
16
|
+
export default class LslStreamInlet implements LslInlet {
|
|
17
|
+
public static Class?: LslInletConstructor
|
|
6
18
|
public static waitAfterOpenStreamMs = 100
|
|
19
|
+
public static lsl = LiblslAdapter.getInstance()
|
|
20
|
+
public static handleLslError = handleLslError
|
|
21
|
+
public static freePointer = freePointer
|
|
22
|
+
|
|
23
|
+
public isRunning = false
|
|
7
24
|
|
|
8
25
|
private sourceId: string
|
|
9
26
|
private chunkSize: number
|
|
10
27
|
private maxBufferedMs: number
|
|
11
28
|
private pullTimeoutMs: number
|
|
12
29
|
private openStreamTimeoutMs: number
|
|
13
|
-
private waitAfterOpenStreamMs: number
|
|
14
30
|
private waitBetweenPullsMs: number
|
|
15
31
|
private flushInletOnStop: boolean
|
|
16
32
|
private onData: OnDataCallback
|
|
17
33
|
|
|
18
|
-
private
|
|
19
|
-
private
|
|
34
|
+
private infoHandle!: InfoHandle
|
|
35
|
+
private inletHandle!: InletHandle
|
|
36
|
+
private channelCount!: number
|
|
37
|
+
|
|
38
|
+
private pullMethod!: () => {
|
|
39
|
+
samples: number[] | undefined
|
|
40
|
+
timestamps: number[] | undefined
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private sampleBuffer!: Buffer<ArrayBuffer>
|
|
44
|
+
private sampleBufferRef!: JsExternal[]
|
|
45
|
+
private sampleBufferPtr!: JsExternal
|
|
46
|
+
|
|
47
|
+
private timestampBuffer!: Buffer<ArrayBuffer>
|
|
48
|
+
private timestampBufferRef!: JsExternal[]
|
|
49
|
+
private timestampBufferPtr!: JsExternal
|
|
50
|
+
|
|
51
|
+
private pullErrorBuffer!: Buffer<ArrayBuffer>
|
|
52
|
+
private pullErrorBufferRef!: JsExternal[]
|
|
53
|
+
private pullErrorBufferPtr!: JsExternal
|
|
54
|
+
|
|
55
|
+
private openStreamErrorBuffer!: Buffer<ArrayBuffer>
|
|
56
|
+
private openStreamErrorBufferRef!: JsExternal[]
|
|
57
|
+
private openStreamErrorBufferPtr!: JsExternal
|
|
20
58
|
|
|
21
59
|
private readonly sixMinutesInMs = 360 * 1000
|
|
22
60
|
private readonly aboutOneYearInMs = 32000000 * 1000
|
|
23
61
|
|
|
24
|
-
|
|
62
|
+
private readonly bytesPerFloat = 4
|
|
63
|
+
private readonly bytesPerDouble = 8
|
|
64
|
+
private readonly bytesPerI32 = 4
|
|
25
65
|
|
|
26
|
-
protected constructor(options:
|
|
66
|
+
protected constructor(options: LslInletOptions, onData: OnDataCallback) {
|
|
27
67
|
const {
|
|
28
68
|
sourceId,
|
|
29
69
|
chunkSize,
|
|
30
70
|
maxBufferedMs,
|
|
31
71
|
pullTimeoutMs,
|
|
32
72
|
openStreamTimeoutMs,
|
|
33
|
-
waitAfterOpenStreamMs,
|
|
34
73
|
waitBetweenPullsMs,
|
|
35
74
|
flushInletOnStop,
|
|
36
75
|
} = options ?? {}
|
|
@@ -40,112 +79,301 @@ export default class LslStreamInlet implements StreamInlet {
|
|
|
40
79
|
this.maxBufferedMs = maxBufferedMs ?? this.sixMinutesInMs
|
|
41
80
|
this.pullTimeoutMs = pullTimeoutMs ?? 0
|
|
42
81
|
this.openStreamTimeoutMs = openStreamTimeoutMs ?? this.aboutOneYearInMs
|
|
43
|
-
this.waitAfterOpenStreamMs = waitAfterOpenStreamMs ?? this.defaultWaitMs
|
|
44
82
|
this.waitBetweenPullsMs = waitBetweenPullsMs ?? 1
|
|
45
83
|
this.flushInletOnStop = flushInletOnStop ?? true
|
|
46
84
|
this.onData = onData
|
|
47
|
-
|
|
48
|
-
this.createWorkerThread()
|
|
49
85
|
}
|
|
50
86
|
|
|
51
87
|
public static async Create(
|
|
52
|
-
options:
|
|
88
|
+
options: LslInletOptions,
|
|
53
89
|
onData: OnDataCallback
|
|
54
90
|
) {
|
|
55
91
|
return new (this.Class ?? this)(options, onData)
|
|
56
92
|
}
|
|
57
93
|
|
|
58
|
-
|
|
59
|
-
this.
|
|
60
|
-
|
|
94
|
+
public async startPulling() {
|
|
95
|
+
if (this.isRunning) {
|
|
96
|
+
console.warn('Skipping startPulling: inlet is already running!')
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
this.isRunning = true
|
|
101
|
+
|
|
102
|
+
await this.createInlet()
|
|
103
|
+
void this.pullLoop()
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private async createInlet() {
|
|
107
|
+
this.infoHandle = this.resolveInfoHandle()
|
|
108
|
+
this.inletHandle = this.doCreateInlet()
|
|
109
|
+
this.channelCount = this.getChannelCount()
|
|
110
|
+
|
|
111
|
+
this.allocateWritableBuffers()
|
|
112
|
+
this.setPullMethod()
|
|
113
|
+
|
|
114
|
+
this.openStream()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private resolveInfoHandle() {
|
|
118
|
+
const handles = this.resolveByProp()
|
|
119
|
+
|
|
120
|
+
if (handles.length === 0) {
|
|
121
|
+
this.throwNoStreamFound()
|
|
122
|
+
} else if (handles.length > 1) {
|
|
123
|
+
this.warnMultipleStreamsFound()
|
|
124
|
+
}
|
|
125
|
+
return handles[0]
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private resolveByProp() {
|
|
129
|
+
return this.lsl.resolveByProp({
|
|
130
|
+
prop: 'source_id',
|
|
131
|
+
value: this.sourceId,
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private throwNoStreamFound() {
|
|
136
|
+
throw new Error(`No stream info for sourceId "${this.sourceId}"`)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private warnMultipleStreamsFound() {
|
|
140
|
+
console.warn(
|
|
141
|
+
`Multiple stream infos for sourceId "${this.sourceId}", using the first one.`
|
|
61
142
|
)
|
|
143
|
+
}
|
|
62
144
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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}`)
|
|
84
|
-
}
|
|
145
|
+
private doCreateInlet() {
|
|
146
|
+
return this.lsl.createInlet({
|
|
147
|
+
infoHandle: this.infoHandle,
|
|
148
|
+
maxBufferedMs: this.maxBufferedMs,
|
|
85
149
|
})
|
|
86
150
|
}
|
|
87
151
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
152
|
+
private getChannelCount() {
|
|
153
|
+
return this.lsl.getChannelCount({ infoHandle: this.infoHandle })
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private allocateWritableBuffers() {
|
|
157
|
+
this.allocateDataBuffer()
|
|
158
|
+
this.allocateTimestampBuffer()
|
|
159
|
+
this.allocatePullErrorBuffer()
|
|
160
|
+
this.allocateOpenStreamErrorBuffer()
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private allocateDataBuffer() {
|
|
164
|
+
this.sampleBuffer = Buffer.alloc(
|
|
165
|
+
this.channelCount * this.chunkSize * this.bytesPerFloat
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
this.sampleBufferRef = createPointer({
|
|
169
|
+
paramsType: [DataType.U8Array],
|
|
170
|
+
paramsValue: [this.sampleBuffer],
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
this.sampleBufferPtr = unwrapPointer(this.sampleBufferRef)[0]
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private allocateTimestampBuffer() {
|
|
177
|
+
this.timestampBuffer = Buffer.alloc(
|
|
178
|
+
this.chunkSize * this.bytesPerDouble
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
this.timestampBufferRef = createPointer({
|
|
182
|
+
paramsType: [DataType.U8Array],
|
|
183
|
+
paramsValue: [this.timestampBuffer],
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
this.timestampBufferPtr = unwrapPointer(this.timestampBufferRef)[0]
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private allocatePullErrorBuffer() {
|
|
190
|
+
this.pullErrorBuffer = Buffer.alloc(this.bytesPerI32)
|
|
191
|
+
|
|
192
|
+
this.pullErrorBufferRef = createPointer({
|
|
193
|
+
paramsType: [DataType.U8Array],
|
|
194
|
+
paramsValue: [this.pullErrorBuffer],
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
this.pullErrorBufferPtr = unwrapPointer(this.pullErrorBufferRef)[0]
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private allocateOpenStreamErrorBuffer() {
|
|
201
|
+
this.openStreamErrorBuffer = Buffer.alloc(this.bytesPerI32)
|
|
92
202
|
|
|
93
|
-
|
|
94
|
-
|
|
203
|
+
this.openStreamErrorBufferRef = createPointer({
|
|
204
|
+
paramsType: [DataType.U8Array],
|
|
205
|
+
paramsValue: [this.openStreamErrorBuffer],
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
this.openStreamErrorBufferPtr = unwrapPointer(
|
|
209
|
+
this.openStreamErrorBufferRef
|
|
210
|
+
)[0]
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private setPullMethod() {
|
|
214
|
+
this.pullMethod =
|
|
215
|
+
this.chunkSize === 1 ? this.pullSample : this.pullChunk
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private pullSample = () => {
|
|
219
|
+
const timestampSec = this.doPullsample()
|
|
220
|
+
|
|
221
|
+
if (timestampSec > 0) {
|
|
222
|
+
return {
|
|
223
|
+
samples: this.readSamplesFromBuffer(),
|
|
224
|
+
timestamps: [timestampSec],
|
|
95
225
|
}
|
|
226
|
+
}
|
|
227
|
+
return { samples: undefined, timestamps: undefined }
|
|
228
|
+
}
|
|
96
229
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
230
|
+
private doPullsample() {
|
|
231
|
+
return this.lsl.pullSample({
|
|
232
|
+
inletHandle: this.inletHandle,
|
|
233
|
+
sampleBufferPtr: this.sampleBufferPtr,
|
|
234
|
+
sampleBufferElements: this.channelCount,
|
|
235
|
+
timeoutMs: this.pullTimeoutMs,
|
|
236
|
+
errorCodePtr: this.pullErrorBufferPtr,
|
|
237
|
+
})
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private readSamplesFromBuffer() {
|
|
241
|
+
const floats = new Float32Array(
|
|
242
|
+
this.sampleBuffer.buffer,
|
|
243
|
+
this.sampleBuffer.byteOffset,
|
|
244
|
+
this.chunkSize * this.channelCount
|
|
245
|
+
)
|
|
246
|
+
return Array.from(floats)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private pullChunk = () => {
|
|
250
|
+
const firstTimestampSec = this.doPullChunk()
|
|
251
|
+
|
|
252
|
+
if (firstTimestampSec > 0) {
|
|
253
|
+
return {
|
|
254
|
+
samples: this.readSamplesFromBuffer(),
|
|
255
|
+
timestamps: this.readTimestampsFromBuffer(),
|
|
256
|
+
}
|
|
100
257
|
}
|
|
258
|
+
return { samples: undefined, timestamps: undefined }
|
|
101
259
|
}
|
|
102
260
|
|
|
103
|
-
private
|
|
104
|
-
this.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
pullTimeoutMs: this.pullTimeoutMs,
|
|
113
|
-
waitBetweenPullsMs: this.waitBetweenPullsMs,
|
|
114
|
-
flushInletOnStop: this.flushInletOnStop,
|
|
115
|
-
},
|
|
261
|
+
private doPullChunk() {
|
|
262
|
+
return this.lsl.pullChunk({
|
|
263
|
+
inletHandle: this.inletHandle,
|
|
264
|
+
sampleBufferPtr: this.sampleBufferPtr,
|
|
265
|
+
sampleBufferElements: this.chunkSize * this.channelCount,
|
|
266
|
+
timestampBufferPtr: this.timestampBufferPtr,
|
|
267
|
+
timestampBufferElements: this.chunkSize,
|
|
268
|
+
timeoutMs: this.pullTimeoutMs,
|
|
269
|
+
errorCodePtr: this.pullErrorBufferPtr,
|
|
116
270
|
})
|
|
117
271
|
}
|
|
118
272
|
|
|
119
|
-
private
|
|
120
|
-
|
|
273
|
+
private readTimestampsFromBuffer() {
|
|
274
|
+
const doubles = new Float64Array(
|
|
275
|
+
this.timestampBuffer.buffer,
|
|
276
|
+
this.timestampBuffer.byteOffset,
|
|
277
|
+
this.chunkSize
|
|
278
|
+
)
|
|
279
|
+
return Array.from(doubles)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private openStream() {
|
|
283
|
+
this.lsl.openStream({
|
|
284
|
+
inletHandle: this.inletHandle,
|
|
285
|
+
timeoutMs: this.openStreamTimeoutMs,
|
|
286
|
+
errorCodePtr: this.openStreamErrorBufferPtr,
|
|
287
|
+
})
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private async pullLoop() {
|
|
291
|
+
while (this.isRunning) {
|
|
292
|
+
await this.pullDataOnce()
|
|
293
|
+
await this.waitBetweenPulls()
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
private async pullDataOnce() {
|
|
298
|
+
const { samples, timestamps } = this.pullMethod()
|
|
299
|
+
this.handleLslErrorIfPresent()
|
|
300
|
+
|
|
301
|
+
if (samples && timestamps) {
|
|
302
|
+
this.onData(samples, timestamps)
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
private handleLslErrorIfPresent() {
|
|
307
|
+
const errorCode = this.pullErrorBuffer.readInt32LE()
|
|
308
|
+
this.handleLslError(errorCode)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private async waitBetweenPulls() {
|
|
312
|
+
await new Promise((r) => setTimeout(r, this.waitBetweenPullsMs))
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
public flushInlet() {
|
|
316
|
+
this.lsl.flushInlet({ inletHandle: this.inletHandle })
|
|
121
317
|
}
|
|
122
318
|
|
|
123
319
|
public stopPulling() {
|
|
124
320
|
this.isRunning = false
|
|
125
|
-
this.
|
|
321
|
+
this.closeStream()
|
|
322
|
+
|
|
323
|
+
if (this.flushInletOnStop) {
|
|
324
|
+
this.flushInlet()
|
|
325
|
+
}
|
|
126
326
|
}
|
|
127
327
|
|
|
128
|
-
|
|
129
|
-
this.
|
|
328
|
+
private closeStream() {
|
|
329
|
+
this.lsl.closeStream({ inletHandle: this.inletHandle })
|
|
130
330
|
}
|
|
131
331
|
|
|
132
332
|
public destroy() {
|
|
133
333
|
if (this.isRunning) {
|
|
134
334
|
this.stopPulling()
|
|
135
335
|
}
|
|
136
|
-
|
|
336
|
+
|
|
337
|
+
this.doDestroyInlet()
|
|
338
|
+
this.freeNativePointers()
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private doDestroyInlet() {
|
|
342
|
+
this.lsl.destroyInlet({ inletHandle: this.inletHandle })
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
private freeNativePointers() {
|
|
346
|
+
this.freePointer({
|
|
347
|
+
paramsType: [
|
|
348
|
+
DataType.U8Array,
|
|
349
|
+
DataType.U8Array,
|
|
350
|
+
DataType.U8Array,
|
|
351
|
+
DataType.U8Array,
|
|
352
|
+
],
|
|
353
|
+
paramsValue: [
|
|
354
|
+
this.openStreamErrorBufferPtr,
|
|
355
|
+
this.sampleBufferPtr,
|
|
356
|
+
this.timestampBufferPtr,
|
|
357
|
+
this.pullErrorBufferPtr,
|
|
358
|
+
],
|
|
359
|
+
pointerType: PointerType.CPointer,
|
|
360
|
+
})
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
private get lsl() {
|
|
364
|
+
return LslStreamInlet.lsl
|
|
137
365
|
}
|
|
138
366
|
|
|
139
|
-
private get
|
|
140
|
-
return LslStreamInlet.
|
|
367
|
+
private get handleLslError() {
|
|
368
|
+
return LslStreamInlet.handleLslError
|
|
141
369
|
}
|
|
142
370
|
|
|
143
|
-
private get
|
|
144
|
-
return LslStreamInlet.
|
|
371
|
+
private get freePointer() {
|
|
372
|
+
return LslStreamInlet.freePointer
|
|
145
373
|
}
|
|
146
374
|
}
|
|
147
375
|
|
|
148
|
-
export interface
|
|
376
|
+
export interface LslInlet {
|
|
149
377
|
startPulling(): Promise<void>
|
|
150
378
|
stopPulling(): void
|
|
151
379
|
flushInlet(): void
|
|
@@ -153,17 +381,16 @@ export interface StreamInlet {
|
|
|
153
381
|
readonly isRunning: boolean
|
|
154
382
|
}
|
|
155
383
|
|
|
156
|
-
export type
|
|
157
|
-
options:
|
|
384
|
+
export type LslInletConstructor = new (
|
|
385
|
+
options: LslInletOptions,
|
|
158
386
|
onData: OnDataCallback
|
|
159
|
-
) =>
|
|
387
|
+
) => LslInlet
|
|
160
388
|
|
|
161
|
-
export interface
|
|
389
|
+
export interface LslInletOptions {
|
|
162
390
|
sourceId: string
|
|
163
391
|
chunkSize: number
|
|
164
392
|
maxBufferedMs?: number
|
|
165
393
|
openStreamTimeoutMs?: number
|
|
166
|
-
waitAfterOpenStreamMs?: number
|
|
167
394
|
pullTimeoutMs?: number
|
|
168
395
|
waitBetweenPullsMs?: number
|
|
169
396
|
flushInletOnStop?: boolean
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
ChannelFormat,
|
|
3
|
+
LiblslAdapter,
|
|
4
|
+
LslErrorCode,
|
|
5
|
+
LslSample,
|
|
6
|
+
OutletHandle,
|
|
7
|
+
handleLslError,
|
|
8
|
+
} from '@neurodevs/ndx-native'
|
|
4
9
|
|
|
5
10
|
import {
|
|
6
11
|
assertValidChannelCount,
|
|
@@ -9,10 +14,13 @@ import {
|
|
|
9
14
|
assertValidMaxBufferedMs,
|
|
10
15
|
assertValidSampleRateHz,
|
|
11
16
|
} from '../assertions.js'
|
|
17
|
+
import LslStreamInfo, { LslInfo } from './LslStreamInfo.js'
|
|
12
18
|
|
|
13
|
-
export default class LslStreamOutlet implements
|
|
14
|
-
public static Class?:
|
|
15
|
-
public static
|
|
19
|
+
export default class LslStreamOutlet implements LslOutlet {
|
|
20
|
+
public static Class?: LslOutletConstructor
|
|
21
|
+
public static setTimeout = setTimeout
|
|
22
|
+
public static lsl = LiblslAdapter.getInstance()
|
|
23
|
+
public static handleLslError = handleLslError
|
|
16
24
|
|
|
17
25
|
public readonly name: string
|
|
18
26
|
public readonly type: string
|
|
@@ -26,9 +34,11 @@ export default class LslStreamOutlet implements StreamOutlet {
|
|
|
26
34
|
public readonly manufacturer: string = 'N/A'
|
|
27
35
|
public readonly units: string = 'N/A'
|
|
28
36
|
|
|
29
|
-
private
|
|
37
|
+
private info!: LslInfo
|
|
38
|
+
private handle!: OutletHandle
|
|
39
|
+
private pushSampleToLsl!: (opts: unknown) => LslErrorCode
|
|
30
40
|
|
|
31
|
-
protected constructor(options:
|
|
41
|
+
protected constructor(options: LslOutletOptions) {
|
|
32
42
|
const {
|
|
33
43
|
name,
|
|
34
44
|
type,
|
|
@@ -55,11 +65,10 @@ export default class LslStreamOutlet implements StreamOutlet {
|
|
|
55
65
|
this.units = units ?? this.units
|
|
56
66
|
|
|
57
67
|
this.validateOptions()
|
|
58
|
-
this.createWorkerThread()
|
|
59
68
|
this.createStreamOutlet()
|
|
60
69
|
}
|
|
61
70
|
|
|
62
|
-
public static async Create(options:
|
|
71
|
+
public static async Create(options: LslOutletOptions) {
|
|
63
72
|
const { waitAfterConstructionMs = 10 } = options ?? {}
|
|
64
73
|
|
|
65
74
|
const instance = new (this.Class ?? this)(options)
|
|
@@ -93,72 +102,67 @@ export default class LslStreamOutlet implements StreamOutlet {
|
|
|
93
102
|
)
|
|
94
103
|
}
|
|
95
104
|
|
|
96
|
-
private createWorkerThread() {
|
|
97
|
-
this.worker = new this.Worker(
|
|
98
|
-
new URL(
|
|
99
|
-
'./workers/outlet/LslStreamOutlet.worker.js',
|
|
100
|
-
import.meta.url
|
|
101
|
-
)
|
|
102
|
-
)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
105
|
private createStreamOutlet() {
|
|
106
|
-
this.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
pushMethod: this.pushMethod,
|
|
121
|
-
},
|
|
106
|
+
this.info = LslStreamInfo.Create({
|
|
107
|
+
name: this.name,
|
|
108
|
+
type: this.type,
|
|
109
|
+
sourceId: this.sourceId,
|
|
110
|
+
channelNames: this.channelNames,
|
|
111
|
+
channelFormat: this.channelFormat,
|
|
112
|
+
sampleRateHz: this.sampleRateHz,
|
|
113
|
+
units: this.units,
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
this.handle = this.lsl.createOutlet({
|
|
117
|
+
infoHandle: this.info.infoHandle,
|
|
118
|
+
chunkSize: this.chunkSize,
|
|
119
|
+
maxBufferedMs: this.maxBufferedMs,
|
|
122
120
|
})
|
|
121
|
+
|
|
122
|
+
this.pushSampleToLsl = (
|
|
123
|
+
this.lsl[this.pushMethod] as (opts: unknown) => LslErrorCode
|
|
124
|
+
).bind(this.lsl)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private get lsl() {
|
|
128
|
+
return LslStreamOutlet.lsl
|
|
123
129
|
}
|
|
124
130
|
|
|
125
131
|
private get pushMethod() {
|
|
126
132
|
return this.methodMap[this.channelFormat]
|
|
127
133
|
}
|
|
128
134
|
|
|
129
|
-
private readonly methodMap: Record<
|
|
135
|
+
private readonly methodMap: Record<
|
|
136
|
+
string,
|
|
137
|
+
'pushSampleFloatTimestamp' | 'pushSampleStringTimestamp'
|
|
138
|
+
> = {
|
|
130
139
|
float32: 'pushSampleFloatTimestamp',
|
|
131
140
|
string: 'pushSampleStringTimestamp',
|
|
132
141
|
}
|
|
133
142
|
|
|
134
|
-
public pushSample(sample: LslSample, timestampSec
|
|
135
|
-
this.
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
timestampSec,
|
|
140
|
-
},
|
|
143
|
+
public pushSample(sample: LslSample, timestampSec = this.lsl.localClock()) {
|
|
144
|
+
const errorCode = this.pushSampleToLsl({
|
|
145
|
+
outletHandle: this.handle,
|
|
146
|
+
sample,
|
|
147
|
+
timestampSec,
|
|
141
148
|
})
|
|
142
|
-
}
|
|
143
149
|
|
|
144
|
-
|
|
145
|
-
this.worker.postMessage({
|
|
146
|
-
type: 'destroyOutlet',
|
|
147
|
-
})
|
|
150
|
+
LslStreamOutlet.handleLslError(errorCode)
|
|
148
151
|
}
|
|
149
152
|
|
|
150
|
-
|
|
151
|
-
|
|
153
|
+
public destroy() {
|
|
154
|
+
this.lsl.destroyOutlet({ outletHandle: this.handle })
|
|
155
|
+
this.lsl.destroyStreamInfo({ infoHandle: this.info.infoHandle })
|
|
152
156
|
}
|
|
153
157
|
|
|
154
158
|
private static async waitForSetup(waitAfterConstructionMs: number) {
|
|
155
159
|
await new Promise((resolve) =>
|
|
156
|
-
setTimeout(resolve, waitAfterConstructionMs)
|
|
160
|
+
LslStreamOutlet.setTimeout(resolve, waitAfterConstructionMs)
|
|
157
161
|
)
|
|
158
162
|
}
|
|
159
163
|
}
|
|
160
164
|
|
|
161
|
-
export interface
|
|
165
|
+
export interface LslOutlet {
|
|
162
166
|
pushSample(sample: LslSample, timestampSec?: number): void
|
|
163
167
|
destroy(): void
|
|
164
168
|
readonly name: string
|
|
@@ -174,15 +178,13 @@ export interface StreamOutlet {
|
|
|
174
178
|
readonly manufacturer: string
|
|
175
179
|
}
|
|
176
180
|
|
|
177
|
-
export type
|
|
178
|
-
options: StreamOutletOptions
|
|
179
|
-
) => StreamOutlet
|
|
181
|
+
export type LslOutletConstructor = new (options: LslOutletOptions) => LslOutlet
|
|
180
182
|
|
|
181
|
-
export interface
|
|
183
|
+
export interface LslOutletOptions extends LslOutletConstructorOptions {
|
|
182
184
|
waitAfterConstructionMs?: number
|
|
183
185
|
}
|
|
184
186
|
|
|
185
|
-
export interface
|
|
187
|
+
export interface LslOutletConstructorOptions {
|
|
186
188
|
name: string
|
|
187
189
|
type: string
|
|
188
190
|
sourceId: string
|