@neurodevs/node-lsl 19.0.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 +11 -11
- package/build/__tests__/AbstractPackageTest.js +26 -30
- package/build/__tests__/AbstractPackageTest.js.map +1 -1
- package/build/__tests__/impl/BleDeviceController.test.d.ts +1 -1
- package/build/__tests__/impl/BleDeviceController.test.js +8 -10
- 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 +7 -12
- 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/WindowedClockRegressor.test.js +1 -1
- package/build/__tests__/impl/WindowedClockRegressor.test.js.map +1 -1
- 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 -21
- package/build/impl/LslStreamInlet.js +221 -65
- package/build/impl/LslStreamInlet.js.map +1 -1
- package/build/impl/LslStreamOutlet.d.ts +15 -14
- package/build/impl/LslStreamOutlet.js +32 -36
- 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.js.map +1 -0
- package/build/impl/controllers/BleDeviceController.d.ts +1 -1
- package/build/impl/controllers/BleDeviceController.js +1 -1
- package/build/impl/controllers/BleDeviceController.js.map +1 -1
- package/build/index.d.ts +27 -23
- package/build/index.js +32 -29
- 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/testDoubles/ClockRegressor/FakeClockRegressor.d.ts +1 -1
- 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/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 +4 -3
- package/src/__tests__/AbstractPackageTest.ts +29 -38
- package/src/__tests__/impl/BleDeviceController.test.ts +8 -10
- 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 +17 -24
- package/src/__tests__/impl/LslWebSocketBridge.test.ts +15 -18
- package/src/__tests__/impl/WindowedClockRegressor.test.ts +1 -1
- 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 +305 -81
- package/src/impl/LslStreamOutlet.ts +57 -56
- package/src/impl/LslWebSocketBridge.ts +15 -18
- package/src/impl/controllers/BleDeviceController.ts +1 -1
- package/src/index.ts +38 -34
- package/src/scripts/{runLslEventMarkerEmitter.ts → runLslEmitter.ts} +4 -3
- package/src/scripts/runMuse.ts +1 -1
- package/src/testDoubles/ClockRegressor/FakeClockRegressor.ts +1 -1
- 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 -19
- package/src/testDoubles/LslWsBridge/SpyLslWsBridge.ts +9 -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/WindowedClockRegressor.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.d.ts +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/impl/{WindowedClockRegressor.d.ts → clocks/WindowedClockRegressor.d.ts} +0 -0
- /package/build/impl/{WindowedClockRegressor.js → clocks/WindowedClockRegressor.js} +0 -0
- /package/build/scripts/{discoverMuse.d.ts → runLslEmitter.d.ts} +0 -0
- /package/build/scripts/{runLslEventMarkerEmitter.d.ts → runLslOutlet.d.ts} +0 -0
- /package/src/impl/{WindowedClockRegressor.ts → clocks/WindowedClockRegressor.ts} +0 -0
- /package/src/scripts/{runLslStreamOutlet.ts → runLslOutlet.ts} +0 -0
|
@@ -1,37 +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
|
|
7
|
-
public static
|
|
19
|
+
public static lsl = LiblslAdapter.getInstance()
|
|
20
|
+
public static handleLslError = handleLslError
|
|
21
|
+
public static freePointer = freePointer
|
|
22
|
+
|
|
23
|
+
public isRunning = false
|
|
8
24
|
|
|
9
25
|
private sourceId: string
|
|
10
26
|
private chunkSize: number
|
|
11
27
|
private maxBufferedMs: number
|
|
12
28
|
private pullTimeoutMs: number
|
|
13
29
|
private openStreamTimeoutMs: number
|
|
14
|
-
private waitAfterOpenStreamMs: number
|
|
15
30
|
private waitBetweenPullsMs: number
|
|
16
31
|
private flushInletOnStop: boolean
|
|
17
32
|
private onData: OnDataCallback
|
|
18
33
|
|
|
19
|
-
private
|
|
20
|
-
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
|
|
21
58
|
|
|
22
59
|
private readonly sixMinutesInMs = 360 * 1000
|
|
23
60
|
private readonly aboutOneYearInMs = 32000000 * 1000
|
|
24
61
|
|
|
25
|
-
|
|
62
|
+
private readonly bytesPerFloat = 4
|
|
63
|
+
private readonly bytesPerDouble = 8
|
|
64
|
+
private readonly bytesPerI32 = 4
|
|
26
65
|
|
|
27
|
-
protected constructor(options:
|
|
66
|
+
protected constructor(options: LslInletOptions, onData: OnDataCallback) {
|
|
28
67
|
const {
|
|
29
68
|
sourceId,
|
|
30
69
|
chunkSize,
|
|
31
70
|
maxBufferedMs,
|
|
32
71
|
pullTimeoutMs,
|
|
33
72
|
openStreamTimeoutMs,
|
|
34
|
-
waitAfterOpenStreamMs,
|
|
35
73
|
waitBetweenPullsMs,
|
|
36
74
|
flushInletOnStop,
|
|
37
75
|
} = options ?? {}
|
|
@@ -41,114 +79,301 @@ export default class LslStreamInlet implements StreamInlet {
|
|
|
41
79
|
this.maxBufferedMs = maxBufferedMs ?? this.sixMinutesInMs
|
|
42
80
|
this.pullTimeoutMs = pullTimeoutMs ?? 0
|
|
43
81
|
this.openStreamTimeoutMs = openStreamTimeoutMs ?? this.aboutOneYearInMs
|
|
44
|
-
this.waitAfterOpenStreamMs = waitAfterOpenStreamMs ?? this.defaultWaitMs
|
|
45
82
|
this.waitBetweenPullsMs = waitBetweenPullsMs ?? 1
|
|
46
83
|
this.flushInletOnStop = flushInletOnStop ?? true
|
|
47
84
|
this.onData = onData
|
|
48
|
-
|
|
49
|
-
this.createWorkerThread()
|
|
50
85
|
}
|
|
51
86
|
|
|
52
87
|
public static async Create(
|
|
53
|
-
options:
|
|
88
|
+
options: LslInletOptions,
|
|
54
89
|
onData: OnDataCallback
|
|
55
90
|
) {
|
|
56
91
|
return new (this.Class ?? this)(options, onData)
|
|
57
92
|
}
|
|
58
93
|
|
|
59
|
-
|
|
60
|
-
this.
|
|
61
|
-
|
|
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.`
|
|
62
142
|
)
|
|
143
|
+
}
|
|
63
144
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
case 'ready': {
|
|
69
|
-
this.workerReady = true
|
|
70
|
-
break
|
|
71
|
-
}
|
|
72
|
-
case 'data': {
|
|
73
|
-
const { payload } = msg
|
|
74
|
-
const { samples, timestamps } = payload
|
|
75
|
-
|
|
76
|
-
this.onData(samples, timestamps)
|
|
77
|
-
break
|
|
78
|
-
}
|
|
79
|
-
case 'error': {
|
|
80
|
-
const { error } = msg
|
|
81
|
-
throw new Error(error)
|
|
82
|
-
}
|
|
83
|
-
default:
|
|
84
|
-
throw new Error(`Unknown message type: ${type}`)
|
|
85
|
-
}
|
|
145
|
+
private doCreateInlet() {
|
|
146
|
+
return this.lsl.createInlet({
|
|
147
|
+
infoHandle: this.infoHandle,
|
|
148
|
+
maxBufferedMs: this.maxBufferedMs,
|
|
86
149
|
})
|
|
87
150
|
}
|
|
88
151
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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)
|
|
202
|
+
|
|
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],
|
|
98
225
|
}
|
|
226
|
+
}
|
|
227
|
+
return { samples: undefined, timestamps: undefined }
|
|
228
|
+
}
|
|
99
229
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
+
}
|
|
103
257
|
}
|
|
258
|
+
return { samples: undefined, timestamps: undefined }
|
|
104
259
|
}
|
|
105
260
|
|
|
106
|
-
private
|
|
107
|
-
this.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
pullTimeoutMs: this.pullTimeoutMs,
|
|
116
|
-
waitBetweenPullsMs: this.waitBetweenPullsMs,
|
|
117
|
-
flushInletOnStop: this.flushInletOnStop,
|
|
118
|
-
},
|
|
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,
|
|
119
270
|
})
|
|
120
271
|
}
|
|
121
272
|
|
|
122
|
-
private
|
|
123
|
-
|
|
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 })
|
|
124
317
|
}
|
|
125
318
|
|
|
126
319
|
public stopPulling() {
|
|
127
320
|
this.isRunning = false
|
|
128
|
-
this.
|
|
321
|
+
this.closeStream()
|
|
322
|
+
|
|
323
|
+
if (this.flushInletOnStop) {
|
|
324
|
+
this.flushInlet()
|
|
325
|
+
}
|
|
129
326
|
}
|
|
130
327
|
|
|
131
|
-
|
|
132
|
-
this.
|
|
328
|
+
private closeStream() {
|
|
329
|
+
this.lsl.closeStream({ inletHandle: this.inletHandle })
|
|
133
330
|
}
|
|
134
331
|
|
|
135
332
|
public destroy() {
|
|
136
333
|
if (this.isRunning) {
|
|
137
334
|
this.stopPulling()
|
|
138
335
|
}
|
|
139
|
-
|
|
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
|
|
140
365
|
}
|
|
141
366
|
|
|
142
|
-
private get
|
|
143
|
-
return LslStreamInlet.
|
|
367
|
+
private get handleLslError() {
|
|
368
|
+
return LslStreamInlet.handleLslError
|
|
144
369
|
}
|
|
145
370
|
|
|
146
|
-
private get
|
|
147
|
-
return LslStreamInlet.
|
|
371
|
+
private get freePointer() {
|
|
372
|
+
return LslStreamInlet.freePointer
|
|
148
373
|
}
|
|
149
374
|
}
|
|
150
375
|
|
|
151
|
-
export interface
|
|
376
|
+
export interface LslInlet {
|
|
152
377
|
startPulling(): Promise<void>
|
|
153
378
|
stopPulling(): void
|
|
154
379
|
flushInlet(): void
|
|
@@ -156,17 +381,16 @@ export interface StreamInlet {
|
|
|
156
381
|
readonly isRunning: boolean
|
|
157
382
|
}
|
|
158
383
|
|
|
159
|
-
export type
|
|
160
|
-
options:
|
|
384
|
+
export type LslInletConstructor = new (
|
|
385
|
+
options: LslInletOptions,
|
|
161
386
|
onData: OnDataCallback
|
|
162
|
-
) =>
|
|
387
|
+
) => LslInlet
|
|
163
388
|
|
|
164
|
-
export interface
|
|
389
|
+
export interface LslInletOptions {
|
|
165
390
|
sourceId: string
|
|
166
391
|
chunkSize: number
|
|
167
392
|
maxBufferedMs?: number
|
|
168
393
|
openStreamTimeoutMs?: number
|
|
169
|
-
waitAfterOpenStreamMs?: number
|
|
170
394
|
pullTimeoutMs?: number
|
|
171
395
|
waitBetweenPullsMs?: number
|
|
172
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,11 +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 Worker = Worker
|
|
19
|
+
export default class LslStreamOutlet implements LslOutlet {
|
|
20
|
+
public static Class?: LslOutletConstructor
|
|
16
21
|
public static setTimeout = setTimeout
|
|
22
|
+
public static lsl = LiblslAdapter.getInstance()
|
|
23
|
+
public static handleLslError = handleLslError
|
|
17
24
|
|
|
18
25
|
public readonly name: string
|
|
19
26
|
public readonly type: string
|
|
@@ -27,9 +34,11 @@ export default class LslStreamOutlet implements StreamOutlet {
|
|
|
27
34
|
public readonly manufacturer: string = 'N/A'
|
|
28
35
|
public readonly units: string = 'N/A'
|
|
29
36
|
|
|
30
|
-
private
|
|
37
|
+
private info!: LslInfo
|
|
38
|
+
private handle!: OutletHandle
|
|
39
|
+
private pushSampleToLsl!: (opts: unknown) => LslErrorCode
|
|
31
40
|
|
|
32
|
-
protected constructor(options:
|
|
41
|
+
protected constructor(options: LslOutletOptions) {
|
|
33
42
|
const {
|
|
34
43
|
name,
|
|
35
44
|
type,
|
|
@@ -56,11 +65,10 @@ export default class LslStreamOutlet implements StreamOutlet {
|
|
|
56
65
|
this.units = units ?? this.units
|
|
57
66
|
|
|
58
67
|
this.validateOptions()
|
|
59
|
-
this.createWorkerThread()
|
|
60
68
|
this.createStreamOutlet()
|
|
61
69
|
}
|
|
62
70
|
|
|
63
|
-
public static async Create(options:
|
|
71
|
+
public static async Create(options: LslOutletOptions) {
|
|
64
72
|
const { waitAfterConstructionMs = 10 } = options ?? {}
|
|
65
73
|
|
|
66
74
|
const instance = new (this.Class ?? this)(options)
|
|
@@ -94,62 +102,57 @@ export default class LslStreamOutlet implements StreamOutlet {
|
|
|
94
102
|
)
|
|
95
103
|
}
|
|
96
104
|
|
|
97
|
-
private createWorkerThread() {
|
|
98
|
-
this.worker = new this.Worker(
|
|
99
|
-
new URL(
|
|
100
|
-
'./workers/outlet/LslStreamOutlet.worker.js',
|
|
101
|
-
import.meta.url
|
|
102
|
-
)
|
|
103
|
-
)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
105
|
private createStreamOutlet() {
|
|
107
|
-
this.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
pushMethod: this.pushMethod,
|
|
122
|
-
},
|
|
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,
|
|
123
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
|
|
124
129
|
}
|
|
125
130
|
|
|
126
131
|
private get pushMethod() {
|
|
127
132
|
return this.methodMap[this.channelFormat]
|
|
128
133
|
}
|
|
129
134
|
|
|
130
|
-
private readonly methodMap: Record<
|
|
135
|
+
private readonly methodMap: Record<
|
|
136
|
+
string,
|
|
137
|
+
'pushSampleFloatTimestamp' | 'pushSampleStringTimestamp'
|
|
138
|
+
> = {
|
|
131
139
|
float32: 'pushSampleFloatTimestamp',
|
|
132
140
|
string: 'pushSampleStringTimestamp',
|
|
133
141
|
}
|
|
134
142
|
|
|
135
|
-
public pushSample(sample: LslSample, timestampSec
|
|
136
|
-
this.
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
timestampSec,
|
|
141
|
-
},
|
|
143
|
+
public pushSample(sample: LslSample, timestampSec = this.lsl.localClock()) {
|
|
144
|
+
const errorCode = this.pushSampleToLsl({
|
|
145
|
+
outletHandle: this.handle,
|
|
146
|
+
sample,
|
|
147
|
+
timestampSec,
|
|
142
148
|
})
|
|
143
|
-
}
|
|
144
149
|
|
|
145
|
-
|
|
146
|
-
this.worker.postMessage({
|
|
147
|
-
type: 'destroyOutlet',
|
|
148
|
-
})
|
|
150
|
+
LslStreamOutlet.handleLslError(errorCode)
|
|
149
151
|
}
|
|
150
152
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
+
public destroy() {
|
|
154
|
+
this.lsl.destroyOutlet({ outletHandle: this.handle })
|
|
155
|
+
this.lsl.destroyStreamInfo({ infoHandle: this.info.infoHandle })
|
|
153
156
|
}
|
|
154
157
|
|
|
155
158
|
private static async waitForSetup(waitAfterConstructionMs: number) {
|
|
@@ -159,7 +162,7 @@ export default class LslStreamOutlet implements StreamOutlet {
|
|
|
159
162
|
}
|
|
160
163
|
}
|
|
161
164
|
|
|
162
|
-
export interface
|
|
165
|
+
export interface LslOutlet {
|
|
163
166
|
pushSample(sample: LslSample, timestampSec?: number): void
|
|
164
167
|
destroy(): void
|
|
165
168
|
readonly name: string
|
|
@@ -175,15 +178,13 @@ export interface StreamOutlet {
|
|
|
175
178
|
readonly manufacturer: string
|
|
176
179
|
}
|
|
177
180
|
|
|
178
|
-
export type
|
|
179
|
-
options: StreamOutletOptions
|
|
180
|
-
) => StreamOutlet
|
|
181
|
+
export type LslOutletConstructor = new (options: LslOutletOptions) => LslOutlet
|
|
181
182
|
|
|
182
|
-
export interface
|
|
183
|
+
export interface LslOutletOptions extends LslOutletConstructorOptions {
|
|
183
184
|
waitAfterConstructionMs?: number
|
|
184
185
|
}
|
|
185
186
|
|
|
186
|
-
export interface
|
|
187
|
+
export interface LslOutletConstructorOptions {
|
|
187
188
|
name: string
|
|
188
189
|
type: string
|
|
189
190
|
sourceId: string
|