@neurodevs/node-lsl 18.1.0 → 20.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/build/__tests__/AbstractPackageTest.d.ts +19 -17
  2. package/build/__tests__/AbstractPackageTest.js +54 -39
  3. package/build/__tests__/AbstractPackageTest.js.map +1 -1
  4. package/build/__tests__/impl/BleDeviceController.test.d.ts +6 -1
  5. package/build/__tests__/impl/BleDeviceController.test.js +44 -15
  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 +9 -16
  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/UsbDeviceController.test.d.ts +17 -0
  19. package/build/__tests__/impl/UsbDeviceController.test.js +81 -0
  20. package/build/__tests__/impl/UsbDeviceController.test.js.map +1 -0
  21. package/build/__tests__/impl/WindowedClockRegressor.test.d.ts +19 -0
  22. package/build/__tests__/impl/WindowedClockRegressor.test.js +172 -0
  23. package/build/__tests__/impl/WindowedClockRegressor.test.js.map +1 -0
  24. package/build/assertions.js +1 -1
  25. package/build/assertions.js.map +1 -1
  26. package/build/impl/LslEventMarkerEmitter.d.ts +12 -12
  27. package/build/impl/LslEventMarkerEmitter.js.map +1 -1
  28. package/build/impl/LslStreamInfo.d.ts +7 -7
  29. package/build/impl/LslStreamInfo.js.map +1 -1
  30. package/build/impl/LslStreamInlet.d.ts +63 -20
  31. package/build/impl/LslStreamInlet.js +221 -64
  32. package/build/impl/LslStreamInlet.js.map +1 -1
  33. package/build/impl/LslStreamOutlet.d.ts +16 -14
  34. package/build/impl/LslStreamOutlet.js +34 -37
  35. package/build/impl/LslStreamOutlet.js.map +1 -1
  36. package/build/impl/LslWebSocketBridge.d.ts +10 -10
  37. package/build/impl/LslWebSocketBridge.js.map +1 -1
  38. package/build/impl/clocks/WindowedClockRegressor.d.ts +24 -0
  39. package/build/impl/clocks/WindowedClockRegressor.js +73 -0
  40. package/build/impl/clocks/WindowedClockRegressor.js.map +1 -0
  41. package/build/impl/controllers/BleDeviceController.d.ts +3 -1
  42. package/build/impl/controllers/BleDeviceController.js +9 -1
  43. package/build/impl/controllers/BleDeviceController.js.map +1 -1
  44. package/build/impl/controllers/UsbDeviceController.d.ts +23 -0
  45. package/build/impl/controllers/UsbDeviceController.js +40 -0
  46. package/build/impl/controllers/UsbDeviceController.js.map +1 -0
  47. package/build/index.d.ts +32 -20
  48. package/build/index.js +39 -26
  49. package/build/index.js.map +1 -1
  50. package/build/scripts/{runLslEventMarkerEmitter.js → runLslEmitter.js} +5 -4
  51. package/build/scripts/runLslEmitter.js.map +1 -0
  52. package/build/scripts/{runLslStreamOutlet.js → runLslOutlet.js} +1 -1
  53. package/build/scripts/runLslOutlet.js.map +1 -0
  54. package/build/scripts/runMuse.js +1 -1
  55. package/build/scripts/runMuse.js.map +1 -1
  56. package/build/scripts/runUsb.js +14 -0
  57. package/build/scripts/runUsb.js.map +1 -0
  58. package/build/testDoubles/BleController/FakeBleController.d.ts +3 -1
  59. package/build/testDoubles/BleController/FakeBleController.js +5 -0
  60. package/build/testDoubles/BleController/FakeBleController.js.map +1 -1
  61. package/build/testDoubles/ClockRegressor/FakeClockRegressor.d.ts +17 -0
  62. package/build/testDoubles/ClockRegressor/FakeClockRegressor.js +23 -0
  63. package/build/testDoubles/ClockRegressor/FakeClockRegressor.js.map +1 -0
  64. package/build/testDoubles/{EventMarkerEmitter/FakeEventMarkerEmitter.d.ts → LslEmitter/FakeLslEmitter.d.ts} +4 -4
  65. package/build/testDoubles/{EventMarkerEmitter/FakeEventMarkerEmitter.js → LslEmitter/FakeLslEmitter.js} +7 -7
  66. package/build/testDoubles/LslEmitter/FakeLslEmitter.js.map +1 -0
  67. package/build/testDoubles/{EventMarkerEmitter/SpyEventMarkerEmitter.d.ts → LslEmitter/SpyLslEmitter.d.ts} +4 -4
  68. package/build/testDoubles/{EventMarkerEmitter/SpyEventMarkerEmitter.js → LslEmitter/SpyLslEmitter.js} +3 -3
  69. package/build/testDoubles/LslEmitter/SpyLslEmitter.js.map +1 -0
  70. package/build/testDoubles/LslEmitter/ThrowingLslEmitter.d.ts +9 -0
  71. package/build/testDoubles/{EventMarkerEmitter/ThrowingEventMarkerEmitter.js → LslEmitter/ThrowingLslEmitter.js} +2 -2
  72. package/build/testDoubles/LslEmitter/ThrowingLslEmitter.js.map +1 -0
  73. package/build/testDoubles/{StreamInfo/FakeStreamInfo.d.ts → LslInfo/FakeLslInfo.d.ts} +4 -4
  74. package/build/testDoubles/{StreamInfo/FakeStreamInfo.js → LslInfo/FakeLslInfo.js} +7 -7
  75. package/build/testDoubles/LslInfo/FakeLslInfo.js.map +1 -0
  76. package/build/testDoubles/LslInfo/SpyLslInfo.d.ts +10 -0
  77. package/build/testDoubles/{StreamInfo/SpyStreamInfo.js → LslInfo/SpyLslInfo.js} +4 -4
  78. package/build/testDoubles/LslInfo/SpyLslInfo.js.map +1 -0
  79. package/build/testDoubles/{StreamInlet/FakeStreamInlet.d.ts → LslInlet/FakeLslInlet.d.ts} +4 -4
  80. package/build/testDoubles/{StreamInlet/FakeStreamInlet.js → LslInlet/FakeLslInlet.js} +12 -11
  81. package/build/testDoubles/LslInlet/FakeLslInlet.js.map +1 -0
  82. package/build/testDoubles/LslInlet/SpyLslInlet.d.ts +4 -0
  83. package/build/testDoubles/{StreamInlet/SpyStreamInlet.js → LslInlet/SpyLslInlet.js} +2 -2
  84. package/build/testDoubles/LslInlet/SpyLslInlet.js.map +1 -0
  85. package/build/testDoubles/{StreamOutlet/FakeStreamOutlet.d.ts → LslOutlet/FakeLslOutlet.d.ts} +5 -5
  86. package/build/testDoubles/{StreamOutlet/FakeStreamOutlet.js → LslOutlet/FakeLslOutlet.js} +8 -8
  87. package/build/testDoubles/LslOutlet/FakeLslOutlet.js.map +1 -0
  88. package/build/testDoubles/LslWsBridge/FakeLslWsBridge.d.ts +14 -0
  89. package/build/testDoubles/{WebSocketBridge/FakeWebSocketBridge.js → LslWsBridge/FakeLslWsBridge.js} +14 -14
  90. package/build/testDoubles/LslWsBridge/FakeLslWsBridge.js.map +1 -0
  91. package/build/testDoubles/LslWsBridge/SpyLslWsBridge.d.ts +4 -0
  92. package/build/testDoubles/{WebSocketBridge/SpyLslWebSocketBridge.js → LslWsBridge/SpyLslWsBridge.js} +2 -2
  93. package/build/testDoubles/LslWsBridge/SpyLslWsBridge.js.map +1 -0
  94. package/build/testDoubles/UsbController/FakeUsbController.d.ts +12 -0
  95. package/build/testDoubles/UsbController/FakeUsbController.js +25 -0
  96. package/build/testDoubles/UsbController/FakeUsbController.js.map +1 -0
  97. package/build/testDoubles/consts.d.ts +2 -12
  98. package/build/testDoubles/consts.js +0 -20
  99. package/build/testDoubles/consts.js.map +1 -1
  100. package/build/testDoubles/generateRandomOutletOptions.d.ts +2 -2
  101. package/build/testDoubles/generateRandomOutletOptions.js.map +1 -1
  102. package/package.json +6 -5
  103. package/src/__tests__/AbstractPackageTest.ts +78 -49
  104. package/src/__tests__/impl/BleDeviceController.test.ts +59 -18
  105. package/src/__tests__/impl/LslEventMarkerEmitter.test.ts +19 -24
  106. package/src/__tests__/impl/LslStreamInfo.test.ts +8 -8
  107. package/src/__tests__/impl/LslStreamInlet.test.ts +21 -78
  108. package/src/__tests__/impl/LslStreamOutlet.test.ts +22 -28
  109. package/src/__tests__/impl/LslWebSocketBridge.test.ts +15 -18
  110. package/src/__tests__/impl/UsbDeviceController.test.ts +109 -0
  111. package/src/__tests__/impl/WindowedClockRegressor.test.ts +262 -0
  112. package/src/assertions.ts +1 -2
  113. package/src/impl/LslEventMarkerEmitter.ts +15 -17
  114. package/src/impl/LslStreamInfo.ts +7 -9
  115. package/src/impl/LslStreamInlet.ts +304 -77
  116. package/src/impl/LslStreamOutlet.ts +59 -57
  117. package/src/impl/LslWebSocketBridge.ts +15 -18
  118. package/src/impl/clocks/WindowedClockRegressor.ts +136 -0
  119. package/src/impl/controllers/BleDeviceController.ts +18 -1
  120. package/src/impl/controllers/UsbDeviceController.ts +65 -0
  121. package/src/index.ts +48 -28
  122. package/src/scripts/{runLslEventMarkerEmitter.ts → runLslEmitter.ts} +4 -3
  123. package/src/scripts/runMuse.ts +1 -1
  124. package/src/scripts/runUsb.ts +20 -0
  125. package/src/testDoubles/BleController/FakeBleController.ts +11 -0
  126. package/src/testDoubles/ClockRegressor/FakeClockRegressor.ts +46 -0
  127. package/src/testDoubles/{EventMarkerEmitter/FakeEventMarkerEmitter.ts → LslEmitter/FakeLslEmitter.ts} +10 -10
  128. package/src/testDoubles/{EventMarkerEmitter/SpyEventMarkerEmitter.ts → LslEmitter/SpyLslEmitter.ts} +4 -4
  129. package/src/testDoubles/{EventMarkerEmitter/ThrowingEventMarkerEmitter.ts → LslEmitter/ThrowingLslEmitter.ts} +4 -4
  130. package/src/testDoubles/{StreamInfo/FakeStreamInfo.ts → LslInfo/FakeLslInfo.ts} +9 -9
  131. package/src/testDoubles/{StreamInfo/SpyStreamInfo.ts → LslInfo/SpyLslInfo.ts} +5 -5
  132. package/src/testDoubles/{StreamInlet/FakeStreamInlet.ts → LslInlet/FakeLslInlet.ts} +15 -14
  133. package/src/testDoubles/LslInlet/SpyLslInlet.ts +10 -0
  134. package/src/testDoubles/{StreamOutlet/FakeStreamOutlet.ts → LslOutlet/FakeLslOutlet.ts} +11 -14
  135. package/src/testDoubles/{WebSocketBridge/FakeWebSocketBridge.ts → LslWsBridge/FakeLslWsBridge.ts} +19 -20
  136. package/src/testDoubles/LslWsBridge/SpyLslWsBridge.ts +9 -0
  137. package/src/testDoubles/UsbController/FakeUsbController.ts +34 -0
  138. package/src/testDoubles/consts.ts +3 -23
  139. package/src/testDoubles/generateRandomOutletOptions.ts +2 -2
  140. package/build/consts.d.ts +0 -11
  141. package/build/consts.js +0 -21
  142. package/build/consts.js.map +0 -1
  143. package/build/impl/workers/inlet/LslStreamInlet.worker.d.ts +0 -22
  144. package/build/impl/workers/inlet/LslStreamInlet.worker.js +0 -42
  145. package/build/impl/workers/inlet/LslStreamInlet.worker.js.map +0 -1
  146. package/build/impl/workers/inlet/StreamInletWorker.d.ts +0 -73
  147. package/build/impl/workers/inlet/StreamInletWorker.js +0 -253
  148. package/build/impl/workers/inlet/StreamInletWorker.js.map +0 -1
  149. package/build/impl/workers/outlet/LslStreamOutlet.worker.d.ts +0 -32
  150. package/build/impl/workers/outlet/LslStreamOutlet.worker.js +0 -82
  151. package/build/impl/workers/outlet/LslStreamOutlet.worker.js.map +0 -1
  152. package/build/scripts/discoverMuse.js +0 -40
  153. package/build/scripts/discoverMuse.js.map +0 -1
  154. package/build/scripts/runLslEventMarkerEmitter.js.map +0 -1
  155. package/build/scripts/runLslStreamOutlet.js.map +0 -1
  156. package/build/scripts/testDestroyOutlet.d.ts +0 -1
  157. package/build/scripts/testDestroyOutlet.js +0 -4
  158. package/build/scripts/testDestroyOutlet.js.map +0 -1
  159. package/build/testDoubles/EventMarkerEmitter/FakeEventMarkerEmitter.js.map +0 -1
  160. package/build/testDoubles/EventMarkerEmitter/SpyEventMarkerEmitter.js.map +0 -1
  161. package/build/testDoubles/EventMarkerEmitter/ThrowingEventMarkerEmitter.d.ts +0 -9
  162. package/build/testDoubles/EventMarkerEmitter/ThrowingEventMarkerEmitter.js.map +0 -1
  163. package/build/testDoubles/StreamInfo/FakeStreamInfo.js.map +0 -1
  164. package/build/testDoubles/StreamInfo/SpyStreamInfo.d.ts +0 -10
  165. package/build/testDoubles/StreamInfo/SpyStreamInfo.js.map +0 -1
  166. package/build/testDoubles/StreamInlet/FakeStreamInlet.js.map +0 -1
  167. package/build/testDoubles/StreamInlet/SpyStreamInlet.d.ts +0 -4
  168. package/build/testDoubles/StreamInlet/SpyStreamInlet.js.map +0 -1
  169. package/build/testDoubles/StreamOutlet/FakeStreamOutlet.js.map +0 -1
  170. package/build/testDoubles/WebSocketBridge/FakeWebSocketBridge.d.ts +0 -14
  171. package/build/testDoubles/WebSocketBridge/FakeWebSocketBridge.js.map +0 -1
  172. package/build/testDoubles/WebSocketBridge/SpyLslWebSocketBridge.d.ts +0 -4
  173. package/build/testDoubles/WebSocketBridge/SpyLslWebSocketBridge.js.map +0 -1
  174. package/build/testDoubles/WorkerThreads/FakeWorker.d.ts +0 -27
  175. package/build/testDoubles/WorkerThreads/FakeWorker.js +0 -67
  176. package/build/testDoubles/WorkerThreads/FakeWorker.js.map +0 -1
  177. package/src/consts.ts +0 -21
  178. package/src/impl/workers/inlet/LslStreamInlet.worker.ts +0 -74
  179. package/src/impl/workers/inlet/StreamInletWorker.ts +0 -348
  180. package/src/impl/workers/outlet/LslStreamOutlet.worker.ts +0 -144
  181. package/src/scripts/discoverMuse.ts +0 -50
  182. package/src/scripts/testDestroyOutlet.ts +0 -5
  183. package/src/testDoubles/StreamInlet/SpyStreamInlet.ts +0 -10
  184. package/src/testDoubles/WebSocketBridge/SpyLslWebSocketBridge.ts +0 -9
  185. package/src/testDoubles/WorkerThreads/FakeWorker.ts +0 -99
  186. /package/build/scripts/{discoverMuse.d.ts → runLslEmitter.d.ts} +0 -0
  187. /package/build/scripts/{runLslEventMarkerEmitter.d.ts → runLslOutlet.d.ts} +0 -0
  188. /package/build/scripts/{runLslStreamOutlet.d.ts → runUsb.d.ts} +0 -0
  189. /package/src/scripts/{runLslStreamOutlet.ts → runLslOutlet.ts} +0 -0
@@ -1,36 +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
19
+ public static lsl = LiblslAdapter.getInstance()
20
+ public static handleLslError = handleLslError
21
+ public static freePointer = freePointer
22
+
23
+ public isRunning = false
7
24
 
8
25
  private sourceId: string
9
26
  private chunkSize: number
10
27
  private maxBufferedMs: number
11
28
  private pullTimeoutMs: number
12
29
  private openStreamTimeoutMs: number
13
- private waitAfterOpenStreamMs: number
14
30
  private waitBetweenPullsMs: number
15
31
  private flushInletOnStop: boolean
16
32
  private onData: OnDataCallback
17
33
 
18
- private worker!: Worker
19
- 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
20
58
 
21
59
  private readonly sixMinutesInMs = 360 * 1000
22
60
  private readonly aboutOneYearInMs = 32000000 * 1000
23
61
 
24
- public isRunning = false
62
+ private readonly bytesPerFloat = 4
63
+ private readonly bytesPerDouble = 8
64
+ private readonly bytesPerI32 = 4
25
65
 
26
- protected constructor(options: StreamInletOptions, onData: OnDataCallback) {
66
+ protected constructor(options: LslInletOptions, onData: OnDataCallback) {
27
67
  const {
28
68
  sourceId,
29
69
  chunkSize,
30
70
  maxBufferedMs,
31
71
  pullTimeoutMs,
32
72
  openStreamTimeoutMs,
33
- waitAfterOpenStreamMs,
34
73
  waitBetweenPullsMs,
35
74
  flushInletOnStop,
36
75
  } = options ?? {}
@@ -40,112 +79,301 @@ export default class LslStreamInlet implements StreamInlet {
40
79
  this.maxBufferedMs = maxBufferedMs ?? this.sixMinutesInMs
41
80
  this.pullTimeoutMs = pullTimeoutMs ?? 0
42
81
  this.openStreamTimeoutMs = openStreamTimeoutMs ?? this.aboutOneYearInMs
43
- this.waitAfterOpenStreamMs = waitAfterOpenStreamMs ?? this.defaultWaitMs
44
82
  this.waitBetweenPullsMs = waitBetweenPullsMs ?? 1
45
83
  this.flushInletOnStop = flushInletOnStop ?? true
46
84
  this.onData = onData
47
-
48
- this.createWorkerThread()
49
85
  }
50
86
 
51
87
  public static async Create(
52
- options: StreamInletOptions,
88
+ options: LslInletOptions,
53
89
  onData: OnDataCallback
54
90
  ) {
55
91
  return new (this.Class ?? this)(options, onData)
56
92
  }
57
93
 
58
- private createWorkerThread() {
59
- this.worker = new this.Worker(
60
- 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.`
61
142
  )
143
+ }
62
144
 
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}`)
84
- }
145
+ private doCreateInlet() {
146
+ return this.lsl.createInlet({
147
+ infoHandle: this.infoHandle,
148
+ maxBufferedMs: this.maxBufferedMs,
85
149
  })
86
150
  }
87
151
 
88
- public async startPulling() {
89
- if (!this.isRunning) {
90
- this.isRunning = true
91
- this.postCreate()
152
+ private getChannelCount() {
153
+ return this.lsl.getChannelCount({ infoHandle: this.infoHandle })
154
+ }
155
+
156
+ private allocateWritableBuffers() {
157
+ this.allocateDataBuffer()
158
+ this.allocateTimestampBuffer()
159
+ this.allocatePullErrorBuffer()
160
+ this.allocateOpenStreamErrorBuffer()
161
+ }
162
+
163
+ private allocateDataBuffer() {
164
+ this.sampleBuffer = Buffer.alloc(
165
+ this.channelCount * this.chunkSize * this.bytesPerFloat
166
+ )
167
+
168
+ this.sampleBufferRef = createPointer({
169
+ paramsType: [DataType.U8Array],
170
+ paramsValue: [this.sampleBuffer],
171
+ })
172
+
173
+ this.sampleBufferPtr = unwrapPointer(this.sampleBufferRef)[0]
174
+ }
175
+
176
+ private allocateTimestampBuffer() {
177
+ this.timestampBuffer = Buffer.alloc(
178
+ this.chunkSize * this.bytesPerDouble
179
+ )
180
+
181
+ this.timestampBufferRef = createPointer({
182
+ paramsType: [DataType.U8Array],
183
+ paramsValue: [this.timestampBuffer],
184
+ })
185
+
186
+ this.timestampBufferPtr = unwrapPointer(this.timestampBufferRef)[0]
187
+ }
188
+
189
+ private allocatePullErrorBuffer() {
190
+ this.pullErrorBuffer = Buffer.alloc(this.bytesPerI32)
191
+
192
+ this.pullErrorBufferRef = createPointer({
193
+ paramsType: [DataType.U8Array],
194
+ paramsValue: [this.pullErrorBuffer],
195
+ })
196
+
197
+ this.pullErrorBufferPtr = unwrapPointer(this.pullErrorBufferRef)[0]
198
+ }
199
+
200
+ private allocateOpenStreamErrorBuffer() {
201
+ this.openStreamErrorBuffer = Buffer.alloc(this.bytesPerI32)
92
202
 
93
- while (!this.workerReady) {
94
- await new Promise((resolve) => setTimeout(resolve, 10))
203
+ this.openStreamErrorBufferRef = createPointer({
204
+ paramsType: [DataType.U8Array],
205
+ paramsValue: [this.openStreamErrorBuffer],
206
+ })
207
+
208
+ this.openStreamErrorBufferPtr = unwrapPointer(
209
+ this.openStreamErrorBufferRef
210
+ )[0]
211
+ }
212
+
213
+ private setPullMethod() {
214
+ this.pullMethod =
215
+ this.chunkSize === 1 ? this.pullSample : this.pullChunk
216
+ }
217
+
218
+ private pullSample = () => {
219
+ const timestampSec = this.doPullsample()
220
+
221
+ if (timestampSec > 0) {
222
+ return {
223
+ samples: this.readSamplesFromBuffer(),
224
+ timestamps: [timestampSec],
95
225
  }
226
+ }
227
+ return { samples: undefined, timestamps: undefined }
228
+ }
96
229
 
97
- this.postStartPulling()
98
- } else {
99
- 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
+ }
100
257
  }
258
+ return { samples: undefined, timestamps: undefined }
101
259
  }
102
260
 
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
- },
261
+ private doPullChunk() {
262
+ return this.lsl.pullChunk({
263
+ inletHandle: this.inletHandle,
264
+ sampleBufferPtr: this.sampleBufferPtr,
265
+ sampleBufferElements: this.chunkSize * this.channelCount,
266
+ timestampBufferPtr: this.timestampBufferPtr,
267
+ timestampBufferElements: this.chunkSize,
268
+ timeoutMs: this.pullTimeoutMs,
269
+ errorCodePtr: this.pullErrorBufferPtr,
116
270
  })
117
271
  }
118
272
 
119
- private postStartPulling() {
120
- 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 })
121
317
  }
122
318
 
123
319
  public stopPulling() {
124
320
  this.isRunning = false
125
- this.worker.postMessage({ type: 'stopPulling' })
321
+ this.closeStream()
322
+
323
+ if (this.flushInletOnStop) {
324
+ this.flushInlet()
325
+ }
126
326
  }
127
327
 
128
- public flushInlet() {
129
- this.worker.postMessage({ type: 'flushInlet' })
328
+ private closeStream() {
329
+ this.lsl.closeStream({ inletHandle: this.inletHandle })
130
330
  }
131
331
 
132
332
  public destroy() {
133
333
  if (this.isRunning) {
134
334
  this.stopPulling()
135
335
  }
136
- 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
137
365
  }
138
366
 
139
- private get defaultWaitMs() {
140
- return LslStreamInlet.waitAfterOpenStreamMs
367
+ private get handleLslError() {
368
+ return LslStreamInlet.handleLslError
141
369
  }
142
370
 
143
- private get Worker() {
144
- return LslStreamInlet.Worker
371
+ private get freePointer() {
372
+ return LslStreamInlet.freePointer
145
373
  }
146
374
  }
147
375
 
148
- export interface StreamInlet {
376
+ export interface LslInlet {
149
377
  startPulling(): Promise<void>
150
378
  stopPulling(): void
151
379
  flushInlet(): void
@@ -153,17 +381,16 @@ export interface StreamInlet {
153
381
  readonly isRunning: boolean
154
382
  }
155
383
 
156
- export type StreamInletConstructor = new (
157
- options: StreamInletOptions,
384
+ export type LslInletConstructor = new (
385
+ options: LslInletOptions,
158
386
  onData: OnDataCallback
159
- ) => StreamInlet
387
+ ) => LslInlet
160
388
 
161
- export interface StreamInletOptions {
389
+ export interface LslInletOptions {
162
390
  sourceId: string
163
391
  chunkSize: number
164
392
  maxBufferedMs?: number
165
393
  openStreamTimeoutMs?: number
166
- waitAfterOpenStreamMs?: number
167
394
  pullTimeoutMs?: number
168
395
  waitBetweenPullsMs?: number
169
396
  flushInletOnStop?: boolean
@@ -1,6 +1,11 @@
1
- import { 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,10 +14,13 @@ import {
9
14
  assertValidMaxBufferedMs,
10
15
  assertValidSampleRateHz,
11
16
  } from '../assertions.js'
17
+ import LslStreamInfo, { LslInfo } from './LslStreamInfo.js'
12
18
 
13
- export default class LslStreamOutlet implements StreamOutlet {
14
- public static Class?: StreamOutletConstructor
15
- public static Worker = Worker
19
+ export default class LslStreamOutlet implements LslOutlet {
20
+ public static Class?: LslOutletConstructor
21
+ public static setTimeout = setTimeout
22
+ public static lsl = LiblslAdapter.getInstance()
23
+ public static handleLslError = handleLslError
16
24
 
17
25
  public readonly name: string
18
26
  public readonly type: string
@@ -26,9 +34,11 @@ export default class LslStreamOutlet implements StreamOutlet {
26
34
  public readonly manufacturer: string = 'N/A'
27
35
  public readonly units: string = 'N/A'
28
36
 
29
- private worker!: Worker
37
+ private info!: LslInfo
38
+ private handle!: OutletHandle
39
+ private pushSampleToLsl!: (opts: unknown) => LslErrorCode
30
40
 
31
- protected constructor(options: StreamOutletOptions) {
41
+ protected constructor(options: LslOutletOptions) {
32
42
  const {
33
43
  name,
34
44
  type,
@@ -55,11 +65,10 @@ export default class LslStreamOutlet implements StreamOutlet {
55
65
  this.units = units ?? this.units
56
66
 
57
67
  this.validateOptions()
58
- this.createWorkerThread()
59
68
  this.createStreamOutlet()
60
69
  }
61
70
 
62
- public static async Create(options: StreamOutletOptions) {
71
+ public static async Create(options: LslOutletOptions) {
63
72
  const { waitAfterConstructionMs = 10 } = options ?? {}
64
73
 
65
74
  const instance = new (this.Class ?? this)(options)
@@ -93,72 +102,67 @@ export default class LslStreamOutlet implements StreamOutlet {
93
102
  )
94
103
  }
95
104
 
96
- private createWorkerThread() {
97
- this.worker = new this.Worker(
98
- new URL(
99
- './workers/outlet/LslStreamOutlet.worker.js',
100
- import.meta.url
101
- )
102
- )
103
- }
104
-
105
105
  private createStreamOutlet() {
106
- this.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
- },
106
+ this.info = LslStreamInfo.Create({
107
+ name: this.name,
108
+ type: this.type,
109
+ sourceId: this.sourceId,
110
+ channelNames: this.channelNames,
111
+ channelFormat: this.channelFormat,
112
+ sampleRateHz: this.sampleRateHz,
113
+ units: this.units,
114
+ })
115
+
116
+ this.handle = this.lsl.createOutlet({
117
+ infoHandle: this.info.infoHandle,
118
+ chunkSize: this.chunkSize,
119
+ maxBufferedMs: this.maxBufferedMs,
122
120
  })
121
+
122
+ this.pushSampleToLsl = (
123
+ this.lsl[this.pushMethod] as (opts: unknown) => LslErrorCode
124
+ ).bind(this.lsl)
125
+ }
126
+
127
+ private get lsl() {
128
+ return LslStreamOutlet.lsl
123
129
  }
124
130
 
125
131
  private get pushMethod() {
126
132
  return this.methodMap[this.channelFormat]
127
133
  }
128
134
 
129
- private readonly methodMap: Record<string, keyof Liblsl> = {
135
+ private readonly methodMap: Record<
136
+ string,
137
+ 'pushSampleFloatTimestamp' | 'pushSampleStringTimestamp'
138
+ > = {
130
139
  float32: 'pushSampleFloatTimestamp',
131
140
  string: 'pushSampleStringTimestamp',
132
141
  }
133
142
 
134
- public pushSample(sample: LslSample, timestampSec?: number) {
135
- this.worker.postMessage({
136
- type: 'pushSample',
137
- payload: {
138
- sample,
139
- timestampSec,
140
- },
143
+ public pushSample(sample: LslSample, timestampSec = this.lsl.localClock()) {
144
+ const errorCode = this.pushSampleToLsl({
145
+ outletHandle: this.handle,
146
+ sample,
147
+ timestampSec,
141
148
  })
142
- }
143
149
 
144
- public destroy() {
145
- this.worker.postMessage({
146
- type: 'destroyOutlet',
147
- })
150
+ LslStreamOutlet.handleLslError(errorCode)
148
151
  }
149
152
 
150
- private get Worker() {
151
- return LslStreamOutlet.Worker
153
+ public destroy() {
154
+ this.lsl.destroyOutlet({ outletHandle: this.handle })
155
+ this.lsl.destroyStreamInfo({ infoHandle: this.info.infoHandle })
152
156
  }
153
157
 
154
158
  private static async waitForSetup(waitAfterConstructionMs: number) {
155
159
  await new Promise((resolve) =>
156
- setTimeout(resolve, waitAfterConstructionMs)
160
+ LslStreamOutlet.setTimeout(resolve, waitAfterConstructionMs)
157
161
  )
158
162
  }
159
163
  }
160
164
 
161
- export interface StreamOutlet {
165
+ export interface LslOutlet {
162
166
  pushSample(sample: LslSample, timestampSec?: number): void
163
167
  destroy(): void
164
168
  readonly name: string
@@ -174,15 +178,13 @@ export interface StreamOutlet {
174
178
  readonly manufacturer: string
175
179
  }
176
180
 
177
- export type StreamOutletConstructor = new (
178
- options: StreamOutletOptions
179
- ) => StreamOutlet
181
+ export type LslOutletConstructor = new (options: LslOutletOptions) => LslOutlet
180
182
 
181
- export interface StreamOutletOptions extends StreamOutletConstructorOptions {
183
+ export interface LslOutletOptions extends LslOutletConstructorOptions {
182
184
  waitAfterConstructionMs?: number
183
185
  }
184
186
 
185
- export interface StreamOutletConstructorOptions {
187
+ export interface LslOutletConstructorOptions {
186
188
  name: string
187
189
  type: string
188
190
  sourceId: string