@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.
Files changed (168) hide show
  1. package/build/__tests__/AbstractPackageTest.d.ts +11 -11
  2. package/build/__tests__/AbstractPackageTest.js +26 -30
  3. package/build/__tests__/AbstractPackageTest.js.map +1 -1
  4. package/build/__tests__/impl/BleDeviceController.test.d.ts +1 -1
  5. package/build/__tests__/impl/BleDeviceController.test.js +8 -10
  6. package/build/__tests__/impl/BleDeviceController.test.js.map +1 -1
  7. package/build/__tests__/impl/LslEventMarkerEmitter.test.js +13 -13
  8. package/build/__tests__/impl/LslEventMarkerEmitter.test.js.map +1 -1
  9. package/build/__tests__/impl/LslStreamInfo.test.js +2 -2
  10. package/build/__tests__/impl/LslStreamInfo.test.js.map +1 -1
  11. package/build/__tests__/impl/LslStreamInlet.test.d.ts +3 -5
  12. package/build/__tests__/impl/LslStreamInlet.test.js +13 -46
  13. package/build/__tests__/impl/LslStreamInlet.test.js.map +1 -1
  14. package/build/__tests__/impl/LslStreamOutlet.test.js +7 -12
  15. package/build/__tests__/impl/LslStreamOutlet.test.js.map +1 -1
  16. package/build/__tests__/impl/LslWebSocketBridge.test.js +9 -10
  17. package/build/__tests__/impl/LslWebSocketBridge.test.js.map +1 -1
  18. package/build/__tests__/impl/WindowedClockRegressor.test.js +1 -1
  19. package/build/__tests__/impl/WindowedClockRegressor.test.js.map +1 -1
  20. package/build/assertions.js +1 -1
  21. package/build/assertions.js.map +1 -1
  22. package/build/impl/LslEventMarkerEmitter.d.ts +12 -12
  23. package/build/impl/LslEventMarkerEmitter.js.map +1 -1
  24. package/build/impl/LslStreamInfo.d.ts +7 -7
  25. package/build/impl/LslStreamInfo.js.map +1 -1
  26. package/build/impl/LslStreamInlet.d.ts +63 -21
  27. package/build/impl/LslStreamInlet.js +221 -65
  28. package/build/impl/LslStreamInlet.js.map +1 -1
  29. package/build/impl/LslStreamOutlet.d.ts +15 -14
  30. package/build/impl/LslStreamOutlet.js +32 -36
  31. package/build/impl/LslStreamOutlet.js.map +1 -1
  32. package/build/impl/LslWebSocketBridge.d.ts +10 -10
  33. package/build/impl/LslWebSocketBridge.js.map +1 -1
  34. package/build/impl/clocks/WindowedClockRegressor.js.map +1 -0
  35. package/build/impl/controllers/BleDeviceController.d.ts +1 -1
  36. package/build/impl/controllers/BleDeviceController.js +1 -1
  37. package/build/impl/controllers/BleDeviceController.js.map +1 -1
  38. package/build/index.d.ts +27 -23
  39. package/build/index.js +32 -29
  40. package/build/index.js.map +1 -1
  41. package/build/scripts/{runLslEventMarkerEmitter.js → runLslEmitter.js} +5 -4
  42. package/build/scripts/runLslEmitter.js.map +1 -0
  43. package/build/scripts/{runLslStreamOutlet.js → runLslOutlet.js} +1 -1
  44. package/build/scripts/runLslOutlet.js.map +1 -0
  45. package/build/scripts/runMuse.js +1 -1
  46. package/build/scripts/runMuse.js.map +1 -1
  47. package/build/testDoubles/ClockRegressor/FakeClockRegressor.d.ts +1 -1
  48. package/build/testDoubles/{EventMarkerEmitter/FakeEventMarkerEmitter.d.ts → LslEmitter/FakeLslEmitter.d.ts} +4 -4
  49. package/build/testDoubles/{EventMarkerEmitter/FakeEventMarkerEmitter.js → LslEmitter/FakeLslEmitter.js} +7 -7
  50. package/build/testDoubles/LslEmitter/FakeLslEmitter.js.map +1 -0
  51. package/build/testDoubles/{EventMarkerEmitter/SpyEventMarkerEmitter.d.ts → LslEmitter/SpyLslEmitter.d.ts} +4 -4
  52. package/build/testDoubles/{EventMarkerEmitter/SpyEventMarkerEmitter.js → LslEmitter/SpyLslEmitter.js} +3 -3
  53. package/build/testDoubles/LslEmitter/SpyLslEmitter.js.map +1 -0
  54. package/build/testDoubles/LslEmitter/ThrowingLslEmitter.d.ts +9 -0
  55. package/build/testDoubles/{EventMarkerEmitter/ThrowingEventMarkerEmitter.js → LslEmitter/ThrowingLslEmitter.js} +2 -2
  56. package/build/testDoubles/LslEmitter/ThrowingLslEmitter.js.map +1 -0
  57. package/build/testDoubles/{StreamInfo/FakeStreamInfo.d.ts → LslInfo/FakeLslInfo.d.ts} +4 -4
  58. package/build/testDoubles/{StreamInfo/FakeStreamInfo.js → LslInfo/FakeLslInfo.js} +7 -7
  59. package/build/testDoubles/LslInfo/FakeLslInfo.js.map +1 -0
  60. package/build/testDoubles/LslInfo/SpyLslInfo.d.ts +10 -0
  61. package/build/testDoubles/{StreamInfo/SpyStreamInfo.js → LslInfo/SpyLslInfo.js} +4 -4
  62. package/build/testDoubles/LslInfo/SpyLslInfo.js.map +1 -0
  63. package/build/testDoubles/{StreamInlet/FakeStreamInlet.d.ts → LslInlet/FakeLslInlet.d.ts} +4 -4
  64. package/build/testDoubles/{StreamInlet/FakeStreamInlet.js → LslInlet/FakeLslInlet.js} +12 -11
  65. package/build/testDoubles/LslInlet/FakeLslInlet.js.map +1 -0
  66. package/build/testDoubles/LslInlet/SpyLslInlet.d.ts +4 -0
  67. package/build/testDoubles/{StreamInlet/SpyStreamInlet.js → LslInlet/SpyLslInlet.js} +2 -2
  68. package/build/testDoubles/LslInlet/SpyLslInlet.js.map +1 -0
  69. package/build/testDoubles/{StreamOutlet/FakeStreamOutlet.d.ts → LslOutlet/FakeLslOutlet.d.ts} +5 -5
  70. package/build/testDoubles/{StreamOutlet/FakeStreamOutlet.js → LslOutlet/FakeLslOutlet.js} +8 -8
  71. package/build/testDoubles/LslOutlet/FakeLslOutlet.js.map +1 -0
  72. package/build/testDoubles/LslWsBridge/FakeLslWsBridge.d.ts +14 -0
  73. package/build/testDoubles/{WebSocketBridge/FakeWebSocketBridge.js → LslWsBridge/FakeLslWsBridge.js} +14 -14
  74. package/build/testDoubles/LslWsBridge/FakeLslWsBridge.js.map +1 -0
  75. package/build/testDoubles/LslWsBridge/SpyLslWsBridge.d.ts +4 -0
  76. package/build/testDoubles/{WebSocketBridge/SpyLslWebSocketBridge.js → LslWsBridge/SpyLslWsBridge.js} +2 -2
  77. package/build/testDoubles/LslWsBridge/SpyLslWsBridge.js.map +1 -0
  78. package/build/testDoubles/consts.d.ts +2 -12
  79. package/build/testDoubles/consts.js +0 -20
  80. package/build/testDoubles/consts.js.map +1 -1
  81. package/build/testDoubles/generateRandomOutletOptions.d.ts +2 -2
  82. package/build/testDoubles/generateRandomOutletOptions.js.map +1 -1
  83. package/package.json +4 -3
  84. package/src/__tests__/AbstractPackageTest.ts +29 -38
  85. package/src/__tests__/impl/BleDeviceController.test.ts +8 -10
  86. package/src/__tests__/impl/LslEventMarkerEmitter.test.ts +19 -24
  87. package/src/__tests__/impl/LslStreamInfo.test.ts +8 -8
  88. package/src/__tests__/impl/LslStreamInlet.test.ts +21 -78
  89. package/src/__tests__/impl/LslStreamOutlet.test.ts +17 -24
  90. package/src/__tests__/impl/LslWebSocketBridge.test.ts +15 -18
  91. package/src/__tests__/impl/WindowedClockRegressor.test.ts +1 -1
  92. package/src/assertions.ts +1 -2
  93. package/src/impl/LslEventMarkerEmitter.ts +15 -17
  94. package/src/impl/LslStreamInfo.ts +7 -9
  95. package/src/impl/LslStreamInlet.ts +305 -81
  96. package/src/impl/LslStreamOutlet.ts +57 -56
  97. package/src/impl/LslWebSocketBridge.ts +15 -18
  98. package/src/impl/controllers/BleDeviceController.ts +1 -1
  99. package/src/index.ts +38 -34
  100. package/src/scripts/{runLslEventMarkerEmitter.ts → runLslEmitter.ts} +4 -3
  101. package/src/scripts/runMuse.ts +1 -1
  102. package/src/testDoubles/ClockRegressor/FakeClockRegressor.ts +1 -1
  103. package/src/testDoubles/{EventMarkerEmitter/FakeEventMarkerEmitter.ts → LslEmitter/FakeLslEmitter.ts} +10 -10
  104. package/src/testDoubles/{EventMarkerEmitter/SpyEventMarkerEmitter.ts → LslEmitter/SpyLslEmitter.ts} +4 -4
  105. package/src/testDoubles/{EventMarkerEmitter/ThrowingEventMarkerEmitter.ts → LslEmitter/ThrowingLslEmitter.ts} +4 -4
  106. package/src/testDoubles/{StreamInfo/FakeStreamInfo.ts → LslInfo/FakeLslInfo.ts} +9 -9
  107. package/src/testDoubles/{StreamInfo/SpyStreamInfo.ts → LslInfo/SpyLslInfo.ts} +5 -5
  108. package/src/testDoubles/{StreamInlet/FakeStreamInlet.ts → LslInlet/FakeLslInlet.ts} +15 -14
  109. package/src/testDoubles/LslInlet/SpyLslInlet.ts +10 -0
  110. package/src/testDoubles/{StreamOutlet/FakeStreamOutlet.ts → LslOutlet/FakeLslOutlet.ts} +11 -14
  111. package/src/testDoubles/{WebSocketBridge/FakeWebSocketBridge.ts → LslWsBridge/FakeLslWsBridge.ts} +19 -19
  112. package/src/testDoubles/LslWsBridge/SpyLslWsBridge.ts +9 -0
  113. package/src/testDoubles/consts.ts +3 -23
  114. package/src/testDoubles/generateRandomOutletOptions.ts +2 -2
  115. package/build/consts.d.ts +0 -11
  116. package/build/consts.js +0 -21
  117. package/build/consts.js.map +0 -1
  118. package/build/impl/WindowedClockRegressor.js.map +0 -1
  119. package/build/impl/workers/inlet/LslStreamInlet.worker.d.ts +0 -22
  120. package/build/impl/workers/inlet/LslStreamInlet.worker.js +0 -42
  121. package/build/impl/workers/inlet/LslStreamInlet.worker.js.map +0 -1
  122. package/build/impl/workers/inlet/StreamInletWorker.d.ts +0 -73
  123. package/build/impl/workers/inlet/StreamInletWorker.js +0 -253
  124. package/build/impl/workers/inlet/StreamInletWorker.js.map +0 -1
  125. package/build/impl/workers/outlet/LslStreamOutlet.worker.d.ts +0 -32
  126. package/build/impl/workers/outlet/LslStreamOutlet.worker.js +0 -82
  127. package/build/impl/workers/outlet/LslStreamOutlet.worker.js.map +0 -1
  128. package/build/scripts/discoverMuse.js +0 -40
  129. package/build/scripts/discoverMuse.js.map +0 -1
  130. package/build/scripts/runLslEventMarkerEmitter.js.map +0 -1
  131. package/build/scripts/runLslStreamOutlet.d.ts +0 -1
  132. package/build/scripts/runLslStreamOutlet.js.map +0 -1
  133. package/build/scripts/testDestroyOutlet.d.ts +0 -1
  134. package/build/scripts/testDestroyOutlet.js +0 -4
  135. package/build/scripts/testDestroyOutlet.js.map +0 -1
  136. package/build/testDoubles/EventMarkerEmitter/FakeEventMarkerEmitter.js.map +0 -1
  137. package/build/testDoubles/EventMarkerEmitter/SpyEventMarkerEmitter.js.map +0 -1
  138. package/build/testDoubles/EventMarkerEmitter/ThrowingEventMarkerEmitter.d.ts +0 -9
  139. package/build/testDoubles/EventMarkerEmitter/ThrowingEventMarkerEmitter.js.map +0 -1
  140. package/build/testDoubles/StreamInfo/FakeStreamInfo.js.map +0 -1
  141. package/build/testDoubles/StreamInfo/SpyStreamInfo.d.ts +0 -10
  142. package/build/testDoubles/StreamInfo/SpyStreamInfo.js.map +0 -1
  143. package/build/testDoubles/StreamInlet/FakeStreamInlet.js.map +0 -1
  144. package/build/testDoubles/StreamInlet/SpyStreamInlet.d.ts +0 -4
  145. package/build/testDoubles/StreamInlet/SpyStreamInlet.js.map +0 -1
  146. package/build/testDoubles/StreamOutlet/FakeStreamOutlet.js.map +0 -1
  147. package/build/testDoubles/WebSocketBridge/FakeWebSocketBridge.d.ts +0 -14
  148. package/build/testDoubles/WebSocketBridge/FakeWebSocketBridge.js.map +0 -1
  149. package/build/testDoubles/WebSocketBridge/SpyLslWebSocketBridge.d.ts +0 -4
  150. package/build/testDoubles/WebSocketBridge/SpyLslWebSocketBridge.js.map +0 -1
  151. package/build/testDoubles/WorkerThreads/FakeWorker.d.ts +0 -27
  152. package/build/testDoubles/WorkerThreads/FakeWorker.js +0 -67
  153. package/build/testDoubles/WorkerThreads/FakeWorker.js.map +0 -1
  154. package/src/consts.ts +0 -21
  155. package/src/impl/workers/inlet/LslStreamInlet.worker.ts +0 -74
  156. package/src/impl/workers/inlet/StreamInletWorker.ts +0 -348
  157. package/src/impl/workers/outlet/LslStreamOutlet.worker.ts +0 -144
  158. package/src/scripts/discoverMuse.ts +0 -50
  159. package/src/scripts/testDestroyOutlet.ts +0 -5
  160. package/src/testDoubles/StreamInlet/SpyStreamInlet.ts +0 -10
  161. package/src/testDoubles/WebSocketBridge/SpyLslWebSocketBridge.ts +0 -9
  162. package/src/testDoubles/WorkerThreads/FakeWorker.ts +0 -99
  163. /package/build/impl/{WindowedClockRegressor.d.ts → clocks/WindowedClockRegressor.d.ts} +0 -0
  164. /package/build/impl/{WindowedClockRegressor.js → clocks/WindowedClockRegressor.js} +0 -0
  165. /package/build/scripts/{discoverMuse.d.ts → runLslEmitter.d.ts} +0 -0
  166. /package/build/scripts/{runLslEventMarkerEmitter.d.ts → runLslOutlet.d.ts} +0 -0
  167. /package/src/impl/{WindowedClockRegressor.ts → clocks/WindowedClockRegressor.ts} +0 -0
  168. /package/src/scripts/{runLslStreamOutlet.ts → runLslOutlet.ts} +0 -0
@@ -1,37 +1,75 @@
1
- import { Worker } from 'node:worker_threads'
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 StreamInlet {
4
- public static Class?: StreamInletConstructor
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 setTimeout = setTimeout
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 worker!: Worker
20
- private workerReady = false
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
- public isRunning = false
62
+ private readonly bytesPerFloat = 4
63
+ private readonly bytesPerDouble = 8
64
+ private readonly bytesPerI32 = 4
26
65
 
27
- protected constructor(options: StreamInletOptions, onData: OnDataCallback) {
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: StreamInletOptions,
88
+ options: LslInletOptions,
54
89
  onData: OnDataCallback
55
90
  ) {
56
91
  return new (this.Class ?? this)(options, onData)
57
92
  }
58
93
 
59
- private createWorkerThread() {
60
- this.worker = new this.Worker(
61
- new URL('./workers/inlet/LslStreamInlet.worker.js', import.meta.url)
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
- this.worker.on('message', (msg) => {
65
- const { type } = msg
66
-
67
- switch (type) {
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
- public async startPulling() {
90
- if (!this.isRunning) {
91
- this.isRunning = true
92
- this.postCreate()
93
-
94
- while (!this.workerReady) {
95
- await new Promise((resolve) =>
96
- LslStreamInlet.setTimeout(resolve, 10)
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
- this.postStartPulling()
101
- } else {
102
- console.warn('Skipping startPulling: inlet is already running!')
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 postCreate() {
107
- this.worker.postMessage({
108
- type: 'createInlet',
109
- payload: {
110
- sourceId: this.sourceId,
111
- chunkSize: this.chunkSize,
112
- maxBufferedMs: this.maxBufferedMs,
113
- openStreamTimeoutMs: this.openStreamTimeoutMs,
114
- waitAfterOpenStreamMs: this.waitAfterOpenStreamMs,
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 postStartPulling() {
123
- this.worker.postMessage({ type: 'startPulling' })
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.worker.postMessage({ type: 'stopPulling' })
321
+ this.closeStream()
322
+
323
+ if (this.flushInletOnStop) {
324
+ this.flushInlet()
325
+ }
129
326
  }
130
327
 
131
- public flushInlet() {
132
- this.worker.postMessage({ type: 'flushInlet' })
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
- this.worker.postMessage({ type: 'destroyInlet' })
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 defaultWaitMs() {
143
- return LslStreamInlet.waitAfterOpenStreamMs
367
+ private get handleLslError() {
368
+ return LslStreamInlet.handleLslError
144
369
  }
145
370
 
146
- private get Worker() {
147
- return LslStreamInlet.Worker
371
+ private get freePointer() {
372
+ return LslStreamInlet.freePointer
148
373
  }
149
374
  }
150
375
 
151
- export interface StreamInlet {
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 StreamInletConstructor = new (
160
- options: StreamInletOptions,
384
+ export type LslInletConstructor = new (
385
+ options: LslInletOptions,
161
386
  onData: OnDataCallback
162
- ) => StreamInlet
387
+ ) => LslInlet
163
388
 
164
- export interface StreamInletOptions {
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 { Worker } from 'node:worker_threads'
2
-
3
- import { ChannelFormat, Liblsl, LslSample } from '@neurodevs/ndx-native'
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 StreamOutlet {
14
- public static Class?: StreamOutletConstructor
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 worker!: Worker
37
+ private info!: LslInfo
38
+ private handle!: OutletHandle
39
+ private pushSampleToLsl!: (opts: unknown) => LslErrorCode
31
40
 
32
- protected constructor(options: StreamOutletOptions) {
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: StreamOutletOptions) {
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.worker.postMessage({
108
- type: 'createOutlet',
109
- payload: {
110
- infoOptions: {
111
- name: this.name,
112
- type: this.type,
113
- sourceId: this.sourceId,
114
- channelNames: this.channelNames,
115
- channelFormat: this.channelFormat,
116
- sampleRateHz: this.sampleRateHz,
117
- units: this.units,
118
- },
119
- chunkSize: this.chunkSize,
120
- maxBufferedMs: this.maxBufferedMs,
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<string, keyof Liblsl> = {
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?: number) {
136
- this.worker.postMessage({
137
- type: 'pushSample',
138
- payload: {
139
- sample,
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
- public destroy() {
146
- this.worker.postMessage({
147
- type: 'destroyOutlet',
148
- })
150
+ LslStreamOutlet.handleLslError(errorCode)
149
151
  }
150
152
 
151
- private get Worker() {
152
- return LslStreamOutlet.Worker
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 StreamOutlet {
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 StreamOutletConstructor = new (
179
- options: StreamOutletOptions
180
- ) => StreamOutlet
181
+ export type LslOutletConstructor = new (options: LslOutletOptions) => LslOutlet
181
182
 
182
- export interface StreamOutletOptions extends StreamOutletConstructorOptions {
183
+ export interface LslOutletOptions extends LslOutletConstructorOptions {
183
184
  waitAfterConstructionMs?: number
184
185
  }
185
186
 
186
- export interface StreamOutletConstructorOptions {
187
+ export interface LslOutletConstructorOptions {
187
188
  name: string
188
189
  type: string
189
190
  sourceId: string