@neurodevs/node-lsl 17.1.0 → 18.0.0

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