@neurodevs/node-lsl 17.1.1 → 18.1.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 (198) hide show
  1. package/.claude/settings.local.json +10 -0
  2. package/.vscode/launch.json +54 -57
  3. package/.vscode/settings.json +61 -66
  4. package/.vscode/tasks.json +98 -129
  5. package/README.md +20 -44
  6. package/build/__tests__/AbstractPackageTest.d.ts +7 -5
  7. package/build/__tests__/AbstractPackageTest.js +22 -15
  8. package/build/__tests__/AbstractPackageTest.js.map +1 -1
  9. package/build/__tests__/impl/BleDeviceController.test.d.ts +54 -0
  10. package/build/__tests__/impl/BleDeviceController.test.js +333 -0
  11. package/build/__tests__/impl/BleDeviceController.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 +20 -8
  19. package/build/__tests__/impl/LslStreamInlet.test.js +224 -69
  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 +40 -50
  23. package/build/__tests__/impl/LslStreamOutlet.test.js.map +1 -1
  24. package/build/__tests__/impl/LslWebSocketBridge.test.js +19 -20
  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/LslEventMarkerEmitter.d.ts +35 -0
  29. package/build/impl/LslEventMarkerEmitter.js +86 -0
  30. package/build/impl/LslEventMarkerEmitter.js.map +1 -0
  31. package/build/impl/LslStreamInfo.d.ts +4 -8
  32. package/build/impl/LslStreamInfo.js +8 -21
  33. package/build/impl/LslStreamInfo.js.map +1 -1
  34. package/build/impl/LslStreamInlet.d.ts +31 -50
  35. package/build/impl/LslStreamInlet.js +79 -149
  36. package/build/impl/LslStreamInlet.js.map +1 -1
  37. package/build/impl/LslStreamOutlet.d.ts +15 -18
  38. package/build/impl/LslStreamOutlet.js +39 -47
  39. package/build/impl/LslStreamOutlet.js.map +1 -1
  40. package/build/impl/LslWebSocketBridge.d.ts +4 -5
  41. package/build/impl/LslWebSocketBridge.js +10 -10
  42. package/build/impl/LslWebSocketBridge.js.map +1 -1
  43. package/build/impl/controllers/BleDeviceController.d.ts +58 -0
  44. package/build/impl/controllers/BleDeviceController.js +145 -0
  45. package/build/impl/controllers/BleDeviceController.js.map +1 -0
  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 +14 -14
  56. package/build/index.js +16 -16
  57. package/build/index.js.map +1 -1
  58. package/build/scripts/discoverMuse.js +40 -0
  59. package/build/scripts/discoverMuse.js.map +1 -0
  60. package/build/scripts/finalizeHandles.d.ts +2 -0
  61. package/build/scripts/finalizeHandles.js +48 -0
  62. package/build/scripts/finalizeHandles.js.map +1 -0
  63. package/build/scripts/runLslEventMarkerEmitter.d.ts +1 -0
  64. package/build/scripts/runLslEventMarkerEmitter.js +11 -0
  65. package/build/scripts/runLslEventMarkerEmitter.js.map +1 -0
  66. package/build/scripts/runMuse.d.ts +1 -0
  67. package/build/scripts/runMuse.js +40 -0
  68. package/build/scripts/runMuse.js.map +1 -0
  69. package/build/scripts/runOutletToInlet.js +7 -6
  70. package/build/scripts/runOutletToInlet.js.map +1 -1
  71. package/build/scripts/testDestroyOutlet.d.ts +1 -0
  72. package/build/scripts/testDestroyOutlet.js +4 -0
  73. package/build/scripts/testDestroyOutlet.js.map +1 -0
  74. package/build/testDoubles/BleController/FakeBleController.d.ts +24 -0
  75. package/build/testDoubles/BleController/FakeBleController.js +42 -0
  76. package/build/testDoubles/BleController/FakeBleController.js.map +1 -0
  77. package/build/testDoubles/BleController/SpyBleController.d.ts +14 -0
  78. package/build/testDoubles/BleController/SpyBleController.js +48 -0
  79. package/build/testDoubles/BleController/SpyBleController.js.map +1 -0
  80. package/build/testDoubles/EventMarkerEmitter/FakeEventMarkerEmitter.d.ts +17 -0
  81. package/build/testDoubles/EventMarkerEmitter/FakeEventMarkerEmitter.js +30 -0
  82. package/build/testDoubles/EventMarkerEmitter/FakeEventMarkerEmitter.js.map +1 -0
  83. package/build/testDoubles/EventMarkerEmitter/SpyEventMarkerEmitter.d.ts +10 -0
  84. package/build/testDoubles/EventMarkerEmitter/SpyEventMarkerEmitter.js +23 -0
  85. package/build/testDoubles/EventMarkerEmitter/SpyEventMarkerEmitter.js.map +1 -0
  86. package/build/testDoubles/EventMarkerEmitter/ThrowingEventMarkerEmitter.d.ts +9 -0
  87. package/build/testDoubles/EventMarkerEmitter/ThrowingEventMarkerEmitter.js +19 -0
  88. package/build/testDoubles/EventMarkerEmitter/ThrowingEventMarkerEmitter.js.map +1 -0
  89. package/build/testDoubles/StreamInfo/FakeStreamInfo.d.ts +3 -3
  90. package/build/testDoubles/StreamInfo/FakeStreamInfo.js +3 -3
  91. package/build/testDoubles/StreamInfo/FakeStreamInfo.js.map +1 -1
  92. package/build/testDoubles/StreamInlet/FakeStreamInlet.d.ts +8 -10
  93. package/build/testDoubles/StreamInlet/FakeStreamInlet.js +6 -7
  94. package/build/testDoubles/StreamInlet/FakeStreamInlet.js.map +1 -1
  95. package/build/testDoubles/StreamInlet/SpyStreamInlet.d.ts +2 -5
  96. package/build/testDoubles/StreamInlet/SpyStreamInlet.js +2 -8
  97. package/build/testDoubles/StreamInlet/SpyStreamInlet.js.map +1 -1
  98. package/build/testDoubles/StreamOutlet/FakeStreamOutlet.d.ts +7 -9
  99. package/build/testDoubles/StreamOutlet/FakeStreamOutlet.js +8 -8
  100. package/build/testDoubles/StreamOutlet/FakeStreamOutlet.js.map +1 -1
  101. package/build/testDoubles/WebSocketBridge/FakeWebSocketBridge.d.ts +1 -1
  102. package/build/testDoubles/WebSocketBridge/FakeWebSocketBridge.js +1 -1
  103. package/build/testDoubles/WebSocketBridge/FakeWebSocketBridge.js.map +1 -1
  104. package/build/testDoubles/WorkerThreads/FakeWorker.d.ts +27 -0
  105. package/build/testDoubles/WorkerThreads/FakeWorker.js +67 -0
  106. package/build/testDoubles/WorkerThreads/FakeWorker.js.map +1 -0
  107. package/build/testDoubles/generateRandomOutletOptions.js +1 -1
  108. package/build/testDoubles/generateRandomOutletOptions.js.map +1 -1
  109. package/build/validations.js +0 -2
  110. package/build/validations.js.map +1 -1
  111. package/eslint.config.js +3 -0
  112. package/package.json +27 -49
  113. package/prettier.config.js +3 -0
  114. package/src/__tests__/AbstractPackageTest.ts +38 -21
  115. package/src/__tests__/impl/BleDeviceController.test.ts +492 -0
  116. package/src/__tests__/impl/LslEventMarkerEmitter.test.ts +323 -0
  117. package/src/__tests__/impl/LslStreamInfo.test.ts +3 -40
  118. package/src/__tests__/impl/LslStreamInlet.test.ts +344 -97
  119. package/src/__tests__/impl/LslStreamOutlet.test.ts +65 -73
  120. package/src/__tests__/impl/LslWebSocketBridge.test.ts +22 -21
  121. package/src/assertions.ts +2 -1
  122. package/src/impl/LslEventMarkerEmitter.ts +133 -0
  123. package/src/impl/LslStreamInfo.ts +17 -29
  124. package/src/impl/LslStreamInlet.ts +102 -215
  125. package/src/impl/LslStreamOutlet.ts +54 -78
  126. package/src/impl/LslWebSocketBridge.ts +14 -14
  127. package/src/impl/controllers/BleDeviceController.ts +223 -0
  128. package/src/impl/workers/inlet/LslStreamInlet.worker.ts +74 -0
  129. package/src/impl/workers/inlet/StreamInletWorker.ts +348 -0
  130. package/src/impl/workers/outlet/LslStreamOutlet.worker.ts +144 -0
  131. package/src/index.ts +16 -16
  132. package/src/scripts/discoverMuse.ts +50 -0
  133. package/src/scripts/finalizeHandles.ts +63 -0
  134. package/src/scripts/runLslEventMarkerEmitter.ts +15 -0
  135. package/src/scripts/runMuse.ts +50 -0
  136. package/src/scripts/runOutletToInlet.ts +10 -8
  137. package/src/scripts/testDestroyOutlet.ts +5 -0
  138. package/src/testDoubles/BleController/FakeBleController.ts +70 -0
  139. package/src/testDoubles/BleController/SpyBleController.ts +61 -0
  140. package/src/testDoubles/EventMarkerEmitter/FakeEventMarkerEmitter.ts +46 -0
  141. package/src/testDoubles/EventMarkerEmitter/SpyEventMarkerEmitter.ts +31 -0
  142. package/src/testDoubles/EventMarkerEmitter/ThrowingEventMarkerEmitter.ts +29 -0
  143. package/src/testDoubles/StreamInfo/FakeStreamInfo.ts +4 -4
  144. package/src/testDoubles/StreamInlet/FakeStreamInlet.ts +11 -17
  145. package/src/testDoubles/StreamInlet/SpyStreamInlet.ts +3 -16
  146. package/src/testDoubles/StreamOutlet/FakeStreamOutlet.ts +15 -15
  147. package/src/testDoubles/WebSocketBridge/FakeWebSocketBridge.ts +1 -1
  148. package/src/testDoubles/WorkerThreads/FakeWorker.ts +99 -0
  149. package/src/testDoubles/generateRandomOutletOptions.ts +2 -1
  150. package/src/validations.ts +0 -2
  151. package/tsconfig.json +23 -27
  152. package/build/.spruce/settings.json +0 -11
  153. package/build/__tests__/impl/LiblslAdapter.test.d.ts +0 -79
  154. package/build/__tests__/impl/LiblslAdapter.test.js +0 -688
  155. package/build/__tests__/impl/LiblslAdapter.test.js.map +0 -1
  156. package/build/__tests__/impl/LslEventMarkerOutlet.test.d.ts +0 -21
  157. package/build/__tests__/impl/LslEventMarkerOutlet.test.js +0 -160
  158. package/build/__tests__/impl/LslEventMarkerOutlet.test.js.map +0 -1
  159. package/build/handleError.d.ts +0 -8
  160. package/build/handleError.js +0 -25
  161. package/build/handleError.js.map +0 -1
  162. package/build/impl/LiblslAdapter.d.ts +0 -154
  163. package/build/impl/LiblslAdapter.js +0 -317
  164. package/build/impl/LiblslAdapter.js.map +0 -1
  165. package/build/impl/LslEventMarkerOutlet.d.ts +0 -28
  166. package/build/impl/LslEventMarkerOutlet.js +0 -63
  167. package/build/impl/LslEventMarkerOutlet.js.map +0 -1
  168. package/build/scripts/runLslEventMarkerOutlet.js +0 -18
  169. package/build/scripts/runLslEventMarkerOutlet.js.map +0 -1
  170. package/build/testDoubles/EventMarkerOutlet/FakeEventMarkerOutlet.d.ts +0 -13
  171. package/build/testDoubles/EventMarkerOutlet/FakeEventMarkerOutlet.js +0 -26
  172. package/build/testDoubles/EventMarkerOutlet/FakeEventMarkerOutlet.js.map +0 -1
  173. package/build/testDoubles/EventMarkerOutlet/MockEventMarkerOutlet.d.ts +0 -17
  174. package/build/testDoubles/EventMarkerOutlet/MockEventMarkerOutlet.js +0 -46
  175. package/build/testDoubles/EventMarkerOutlet/MockEventMarkerOutlet.js.map +0 -1
  176. package/build/testDoubles/EventMarkerOutlet/SpyEventMarkerOutlet.d.ts +0 -9
  177. package/build/testDoubles/EventMarkerOutlet/SpyEventMarkerOutlet.js +0 -19
  178. package/build/testDoubles/EventMarkerOutlet/SpyEventMarkerOutlet.js.map +0 -1
  179. package/build/testDoubles/EventMarkerOutlet/ThrowingEventMarkerOutlet.d.ts +0 -9
  180. package/build/testDoubles/EventMarkerOutlet/ThrowingEventMarkerOutlet.js +0 -19
  181. package/build/testDoubles/EventMarkerOutlet/ThrowingEventMarkerOutlet.js.map +0 -1
  182. package/build/testDoubles/Liblsl/FakeLiblsl.d.ts +0 -50
  183. package/build/testDoubles/Liblsl/FakeLiblsl.js +0 -126
  184. package/build/testDoubles/Liblsl/FakeLiblsl.js.map +0 -1
  185. package/eslint.config.mjs +0 -3
  186. package/src/.spruce/settings.json +0 -11
  187. package/src/__tests__/impl/LiblslAdapter.test.ts +0 -817
  188. package/src/__tests__/impl/LslEventMarkerOutlet.test.ts +0 -195
  189. package/src/handleError.ts +0 -24
  190. package/src/impl/LiblslAdapter.ts +0 -543
  191. package/src/impl/LslEventMarkerOutlet.ts +0 -97
  192. package/src/scripts/runLslEventMarkerOutlet.ts +0 -23
  193. package/src/testDoubles/EventMarkerOutlet/FakeEventMarkerOutlet.ts +0 -36
  194. package/src/testDoubles/EventMarkerOutlet/MockEventMarkerOutlet.ts +0 -75
  195. package/src/testDoubles/EventMarkerOutlet/SpyEventMarkerOutlet.ts +0 -24
  196. package/src/testDoubles/EventMarkerOutlet/ThrowingEventMarkerOutlet.ts +0 -28
  197. package/src/testDoubles/Liblsl/FakeLiblsl.ts +0 -182
  198. /package/build/scripts/{runLslEventMarkerOutlet.d.ts → discoverMuse.d.ts} +0 -0
@@ -0,0 +1,144 @@
1
+ import { parentPort } from 'node:worker_threads'
2
+
3
+ import {
4
+ LiblslAdapter,
5
+ Liblsl,
6
+ OutletHandle,
7
+ handleLslError,
8
+ LslErrorCode,
9
+ } from '@neurodevs/ndx-native'
10
+
11
+ import LslStreamInfo, {
12
+ StreamInfo,
13
+ StreamInfoOptions,
14
+ } from '../../LslStreamInfo.js'
15
+
16
+ let lsl = LiblslAdapter.getInstance()
17
+
18
+ export function setOutletLiblslAdapter(adapter: Liblsl) {
19
+ LiblslAdapter.setInstance(adapter)
20
+ lsl = LiblslAdapter.getInstance()
21
+ }
22
+
23
+ let handleLslErrorFn = handleLslError
24
+
25
+ export function setOutletHandleLslError(fn: typeof handleLslError) {
26
+ handleLslErrorFn = fn
27
+ }
28
+
29
+ let info: StreamInfo
30
+ let handle: OutletHandle
31
+ let pushMethod: (opts: unknown) => LslErrorCode
32
+
33
+ parentPort?.on('message', (msg: OutletMessage) => {
34
+ const { type } = msg
35
+
36
+ try {
37
+ switch (type) {
38
+ case 'createOutlet': {
39
+ createOutlet(msg)
40
+ break
41
+ }
42
+
43
+ case 'pushSample': {
44
+ pushSample(msg)
45
+ break
46
+ }
47
+
48
+ case 'destroyOutlet': {
49
+ destroyOutlet()
50
+ break
51
+ }
52
+ }
53
+ } catch (err) {
54
+ parentPort?.postMessage({
55
+ type: 'error',
56
+ error: err instanceof Error ? err.message : String(err),
57
+ })
58
+ }
59
+ })
60
+
61
+ export function createOutlet(msg: {
62
+ type: 'createOutlet'
63
+ payload: CreateOutletPayload
64
+ }) {
65
+ const { payload } = msg
66
+
67
+ info = LslStreamInfo.Create(payload.infoOptions)
68
+
69
+ handle = lsl.createOutlet({
70
+ infoHandle: info.infoHandle,
71
+ chunkSize: payload.chunkSize,
72
+ maxBufferedMs: payload.maxBufferedMs,
73
+ })
74
+
75
+ pushMethod = (
76
+ lsl[payload.pushMethod] as (opts: unknown) => LslErrorCode
77
+ ).bind(lsl)
78
+
79
+ parentPort?.postMessage({ type: 'ready' })
80
+ }
81
+
82
+ export function pushSample(msg: {
83
+ type: 'pushSample'
84
+ payload: PushSamplePayload
85
+ }) {
86
+ const { payload } = msg
87
+ const { sample, timestampSec = lsl.localClock() } = payload
88
+
89
+ if (!handle || !pushMethod) {
90
+ throw new Error('Outlet not initialized')
91
+ }
92
+
93
+ const errorCode = pushMethod({
94
+ outletHandle: handle,
95
+ sample,
96
+ timestampSec,
97
+ })
98
+
99
+ try {
100
+ handleLslErrorFn(errorCode)
101
+ } catch {
102
+ parentPort?.postMessage({
103
+ type: 'error',
104
+ error: `Error pushing sample: ${LslErrorCode[errorCode]}`,
105
+ })
106
+ }
107
+ }
108
+
109
+ export function destroyOutlet() {
110
+ if (handle) {
111
+ lsl.destroyOutlet({ outletHandle: handle })
112
+ }
113
+
114
+ if (info) {
115
+ lsl.destroyStreamInfo({ infoHandle: info.infoHandle })
116
+ }
117
+
118
+ parentPort?.close()
119
+ }
120
+
121
+ export type OutletMessage =
122
+ | {
123
+ type: 'createOutlet'
124
+ payload: CreateOutletPayload
125
+ }
126
+ | {
127
+ type: 'pushSample'
128
+ payload: PushSamplePayload
129
+ }
130
+ | {
131
+ type: 'destroyOutlet'
132
+ }
133
+
134
+ export interface CreateOutletPayload {
135
+ infoOptions: StreamInfoOptions
136
+ chunkSize: number
137
+ maxBufferedMs: number
138
+ pushMethod: 'pushSampleFloatTimestamp' | 'pushSampleStringTimestamp'
139
+ }
140
+
141
+ export interface PushSamplePayload {
142
+ sample: unknown
143
+ timestampSec?: number
144
+ }
package/src/index.ts CHANGED
@@ -1,27 +1,27 @@
1
- // EventMarkerOutlet
1
+ // DeviceController
2
2
 
3
- export { default as LslEventMarkerOutlet } from './impl/LslEventMarkerOutlet.js'
4
- export * from './impl/LslEventMarkerOutlet.js'
3
+ export { default as BleDeviceController } from './impl/controllers/BleDeviceController.js'
4
+ export * from './impl/controllers/BleDeviceController.js'
5
5
 
6
- export { default as FakeEventMarkerOutlet } from './testDoubles/EventMarkerOutlet/FakeEventMarkerOutlet.js'
7
- export * from './testDoubles/EventMarkerOutlet/FakeEventMarkerOutlet.js'
6
+ export { default as FakeBleController } from './testDoubles/BleController/FakeBleController.js'
7
+ export * from './testDoubles/BleController/FakeBleController.js'
8
8
 
9
- export { default as MockEventMarkerOutlet } from './testDoubles/EventMarkerOutlet/MockEventMarkerOutlet.js'
10
- export * from './testDoubles/EventMarkerOutlet/MockEventMarkerOutlet.js'
9
+ export { default as SpyBleController } from './testDoubles/BleController/SpyBleController.js'
10
+ export * from './testDoubles/BleController/SpyBleController.js'
11
11
 
12
- export { default as SpyEventMarkerOutlet } from './testDoubles/EventMarkerOutlet/SpyEventMarkerOutlet.js'
13
- export * from './testDoubles/EventMarkerOutlet/SpyEventMarkerOutlet.js'
12
+ // EventMarkerEmitter
14
13
 
15
- export { default as ThrowingEventMarkerOutlet } from './testDoubles/EventMarkerOutlet/ThrowingEventMarkerOutlet.js'
16
- export * from './testDoubles/EventMarkerOutlet/ThrowingEventMarkerOutlet.js'
14
+ export { default as LslEventMarkerEmitter } from './impl/LslEventMarkerEmitter.js'
15
+ export * from './impl/LslEventMarkerEmitter.js'
17
16
 
18
- // Liblsl
17
+ export { default as FakeEventMarkerEmitter } from './testDoubles/EventMarkerEmitter/FakeEventMarkerEmitter.js'
18
+ export * from './testDoubles/EventMarkerEmitter/FakeEventMarkerEmitter.js'
19
19
 
20
- export { default as LiblslAdapter } from './impl/LiblslAdapter.js'
21
- export * from './impl/LiblslAdapter.js'
20
+ export { default as SpyEventMarkerEmitter } from './testDoubles/EventMarkerEmitter/SpyEventMarkerEmitter.js'
21
+ export * from './testDoubles/EventMarkerEmitter/SpyEventMarkerEmitter.js'
22
22
 
23
- export { default as FakeLiblsl } from './testDoubles/Liblsl/FakeLiblsl.js'
24
- export * from './testDoubles/Liblsl/FakeLiblsl.js'
23
+ export { default as ThrowingEventMarkerEmitter } from './testDoubles/EventMarkerEmitter/ThrowingEventMarkerEmitter.js'
24
+ export * from './testDoubles/EventMarkerEmitter/ThrowingEventMarkerEmitter.js'
25
25
 
26
26
  // StreamInfo
27
27
 
@@ -0,0 +1,50 @@
1
+ import koffi from 'koffi'
2
+
3
+ import BleDeviceController from '../impl/controllers/BleDeviceController.js'
4
+
5
+ const MUSE_CHARACTERISTIC_UUIDS: Record<string, string> = {
6
+ CONTROL: '273E0001-4C4D-454D-96BE-F03BAC821358',
7
+ TELEMETRY: '273E000B-4C4D-454D-96BE-F03BAC821358',
8
+ GYROSCOPE: '273E0009-4C4D-454D-96BE-F03BAC821358',
9
+ ACCELEROMETER: '273E000A-4C4D-454D-96BE-F03BAC821358',
10
+ PPG_AMBIENT: '273E000F-4C4D-454D-96BE-F03BAC821358',
11
+ PPG_INFRARED: '273E0010-4C4D-454D-96BE-F03BAC821358',
12
+ PPG_RED: '273E0011-4C4D-454D-96BE-F03BAC821358',
13
+ EEG_TP9: '273E0003-4C4D-454D-96BE-F03BAC821358',
14
+ EEG_AF7: '273E0004-4C4D-454D-96BE-F03BAC821358',
15
+ EEG_AF8: '273E0005-4C4D-454D-96BE-F03BAC821358',
16
+ EEG_TP10: '273E0006-4C4D-454D-96BE-F03BAC821358',
17
+ EEG_AUX: '273E0007-4C4D-454D-96BE-F03BAC821358',
18
+ }
19
+
20
+ const CONTROL_UUID = MUSE_CHARACTERISTIC_UUIDS['CONTROL']
21
+
22
+ const charCallbacks = Object.entries(MUSE_CHARACTERISTIC_UUIDS).map(
23
+ ([name, uuid]) => {
24
+ return {
25
+ charUuid: uuid,
26
+ charName: name,
27
+ onData: (data: Buffer, length: number, timestampSec: number) => {
28
+ const bytes = koffi.decode(data, 'uint8', length) as number[]
29
+ console.info(`[${timestampSec}] length=${length}`, bytes)
30
+ },
31
+ }
32
+ }
33
+ )
34
+
35
+ const muse = await BleDeviceController.Create({
36
+ deviceNamePrefix: 'Muse',
37
+ charCallbacks,
38
+ rssiIntervalMs: 1000,
39
+ })
40
+
41
+ await muse.connect()
42
+
43
+ for (const cmd of ['h', 'p50', 's', 'd']) {
44
+ await muse.writeCharacteristic(CONTROL_UUID, cmd)
45
+ await new Promise((resolve) => setTimeout(resolve, 100))
46
+ }
47
+
48
+ await new Promise((resolve) => setTimeout(resolve, 5 * 1000))
49
+
50
+ await muse.disconnect()
@@ -0,0 +1,63 @@
1
+ export type DestroyHandle = () => void // Do not make this async
2
+
3
+ const destroyHandles = new Set<DestroyHandle>()
4
+
5
+ let isFinalizing = false
6
+
7
+ export function register(destroy: DestroyHandle) {
8
+ if (isFinalizing) {
9
+ destroy()
10
+ return
11
+ }
12
+
13
+ destroyHandles.add(destroy)
14
+
15
+ const unregister = () => {
16
+ destroyHandles.delete(destroy)
17
+ }
18
+
19
+ return unregister
20
+ }
21
+
22
+ function finalizeHandles() {
23
+ if (isFinalizing) {
24
+ return
25
+ }
26
+ isFinalizing = true
27
+
28
+ for (const destroy of destroyHandles) {
29
+ try {
30
+ destroy()
31
+ } catch (err) {
32
+ console.error('Error during finalization:', err)
33
+ }
34
+ }
35
+
36
+ destroyHandles.clear()
37
+ }
38
+
39
+ process.once('SIGINT', finalizeHandles)
40
+
41
+ process.once('SIGTERM', finalizeHandles)
42
+
43
+ process.once('uncaughtException', (err) => {
44
+ console.error('Uncaught exception:', err)
45
+ finalizeHandles()
46
+ process.exit(1)
47
+ })
48
+
49
+ process.once('unhandledRejection', (reason) => {
50
+ console.error('Unhandled rejection:', reason)
51
+ finalizeHandles()
52
+ process.exit(1)
53
+ })
54
+
55
+ process.once('beforeExit', () => {
56
+ if (destroyHandles.size > 0 && !isFinalizing) {
57
+ console.warn(
58
+ 'Warning: process is exiting with active handles.\n' +
59
+ 'Did you forget to call destroy()? Finalizing automatically.'
60
+ )
61
+ finalizeHandles()
62
+ }
63
+ })
@@ -0,0 +1,15 @@
1
+ import LslEventMarkerEmitter from '../impl/LslEventMarkerEmitter.js'
2
+
3
+ const outlet = await LslEventMarkerEmitter.Create()
4
+
5
+ const markers = [
6
+ { name: 'phase-1-begin', waitAfterMs: 100 },
7
+ { name: 'phase-1-end', waitAfterMs: 100 },
8
+ { name: 'never-reached-marker', waitAfterMs: 100 },
9
+ ]
10
+
11
+ void outlet.emitMany(markers)
12
+
13
+ await new Promise((resolve) => setTimeout(resolve, 250))
14
+
15
+ outlet.interrupt()
@@ -0,0 +1,50 @@
1
+ import koffi from 'koffi'
2
+
3
+ import BleDeviceController from '../impl/controllers/BleDeviceController.js'
4
+
5
+ const MUSE_CHARACTERISTIC_UUIDS: Record<string, string> = {
6
+ CONTROL: '273E0001-4C4D-454D-96BE-F03BAC821358',
7
+ TELEMETRY: '273E000B-4C4D-454D-96BE-F03BAC821358',
8
+ GYROSCOPE: '273E0009-4C4D-454D-96BE-F03BAC821358',
9
+ ACCELEROMETER: '273E000A-4C4D-454D-96BE-F03BAC821358',
10
+ PPG_AMBIENT: '273E000F-4C4D-454D-96BE-F03BAC821358',
11
+ PPG_INFRARED: '273E0010-4C4D-454D-96BE-F03BAC821358',
12
+ PPG_RED: '273E0011-4C4D-454D-96BE-F03BAC821358',
13
+ EEG_TP9: '273E0003-4C4D-454D-96BE-F03BAC821358',
14
+ EEG_AF7: '273E0004-4C4D-454D-96BE-F03BAC821358',
15
+ EEG_AF8: '273E0005-4C4D-454D-96BE-F03BAC821358',
16
+ EEG_TP10: '273E0006-4C4D-454D-96BE-F03BAC821358',
17
+ EEG_AUX: '273E0007-4C4D-454D-96BE-F03BAC821358',
18
+ }
19
+
20
+ const CONTROL_UUID = MUSE_CHARACTERISTIC_UUIDS['CONTROL']
21
+
22
+ const charCallbacks = Object.entries(MUSE_CHARACTERISTIC_UUIDS).map(
23
+ ([name, uuid]) => {
24
+ return {
25
+ charUuid: uuid,
26
+ charName: name,
27
+ onData: (data: Buffer, length: number, timestampSec: number) => {
28
+ const bytes = koffi.decode(data, 'uint8', length) as number[]
29
+ console.info(`[${timestampSec}] length=${length}`, bytes)
30
+ },
31
+ }
32
+ }
33
+ )
34
+
35
+ const muse = await BleDeviceController.Create({
36
+ deviceUuid: 'CA6A61B7-B7A8-AF24-3C9E-04A6A5012554',
37
+ charCallbacks,
38
+ rssiIntervalMs: 1000,
39
+ })
40
+
41
+ await muse.connect()
42
+
43
+ for (const cmd of ['h', 'p50', 's', 'd']) {
44
+ await muse.writeCharacteristic(CONTROL_UUID, cmd)
45
+ await new Promise((resolve) => setTimeout(resolve, 100))
46
+ }
47
+
48
+ await new Promise((resolve) => setTimeout(resolve, 5 * 1000))
49
+
50
+ await muse.disconnect()
@@ -1,11 +1,13 @@
1
1
  import LslStreamInlet from '../impl/LslStreamInlet.js'
2
2
  import LslStreamOutlet from '../impl/LslStreamOutlet.js'
3
3
 
4
+ const sourceId = 'TestOutletID'
5
+
4
6
  console.log('Creating outlet...')
5
7
  const outlet = await LslStreamOutlet.Create({
6
8
  name: 'TestOutlet',
7
9
  type: 'TEST',
8
- sourceId: 'TestOutletID',
10
+ sourceId,
9
11
  sampleRateHz: 100,
10
12
  channelNames: ['Ch1', 'Ch2', 'Ch3'],
11
13
  channelFormat: 'float32',
@@ -16,20 +18,18 @@ const outlet = await LslStreamOutlet.Create({
16
18
 
17
19
  console.log('Creating inlet...')
18
20
 
19
- const inletOptions = {
20
- info: outlet.info,
21
- chunkSize: 1,
22
- }
23
-
24
21
  const inlet = await LslStreamInlet.Create(
25
- inletOptions,
22
+ {
23
+ sourceId,
24
+ chunkSize: 1,
25
+ },
26
26
  (samples, timestamps) => {
27
27
  console.log('Received samples:', samples, 'at timestamps:', timestamps)
28
28
  }
29
29
  )
30
30
 
31
31
  console.log('Start pulling...')
32
- inlet.startPulling()
32
+ await inlet.startPulling()
33
33
 
34
34
  console.log('Waiting for 100 ms...')
35
35
  await new Promise((resolve) => setTimeout(resolve, 100))
@@ -49,3 +49,5 @@ for (let i = 0; i < 30; i++) {
49
49
  outlet.pushSample(sample)
50
50
  await new Promise((resolve) => setTimeout(resolve, 100))
51
51
  }
52
+
53
+ process.exit(0)
@@ -0,0 +1,5 @@
1
+ import LslEventMarkerEmitter from '../impl/LslEventMarkerEmitter.js'
2
+
3
+ const emitter = await LslEventMarkerEmitter.Create()
4
+
5
+ emitter.destroy()
@@ -0,0 +1,70 @@
1
+ import generateId from '@neurodevs/generate-id'
2
+ import {
3
+ BleController,
4
+ BleControllerOptions,
5
+ } from '../../impl/controllers/BleDeviceController.js'
6
+
7
+ export default class FakeBleController implements BleController {
8
+ public static callsToConstructor: (BleControllerOptions | undefined)[] = []
9
+
10
+ public static numCallsToConnect = 0
11
+
12
+ public static callsToWriteCharacteristic: {
13
+ characteristicUuid: string
14
+ value: string
15
+ }[] = []
16
+
17
+ public static numCallsToDisconnect = 0
18
+
19
+ public static fakeCharacteristics: Record<string, unknown> = {}
20
+ public static fakeName = `fake-${generateId()}`
21
+
22
+ private _uuid: string
23
+ private _name: string
24
+
25
+ public constructor(options?: BleControllerOptions) {
26
+ const { deviceUuid } = options ?? {}
27
+
28
+ this._uuid = deviceUuid ?? generateId()
29
+ this._name = FakeBleController.fakeName
30
+
31
+ FakeBleController.callsToConstructor.push(options)
32
+ }
33
+
34
+ public async connect() {
35
+ FakeBleController.numCallsToConnect++
36
+ }
37
+
38
+ public async writeCharacteristic(
39
+ characteristicUuid: string,
40
+ value: string
41
+ ) {
42
+ FakeBleController.callsToWriteCharacteristic.push({
43
+ characteristicUuid,
44
+ value,
45
+ })
46
+ }
47
+
48
+ public async disconnect() {
49
+ FakeBleController.numCallsToDisconnect++
50
+ }
51
+
52
+ public get uuid() {
53
+ return this._uuid
54
+ }
55
+
56
+ public get name() {
57
+ return this._name
58
+ }
59
+
60
+ public static resetTestDouble() {
61
+ this.callsToConstructor = []
62
+ this.numCallsToConnect = 0
63
+ this.callsToWriteCharacteristic = []
64
+ this.numCallsToDisconnect = 0
65
+ }
66
+ }
67
+
68
+ export interface CallToBleControllerConstructor {
69
+ options: BleControllerOptions
70
+ }
@@ -0,0 +1,61 @@
1
+ import BleDeviceController, {
2
+ BleControllerOptions,
3
+ } from '../../impl/controllers/BleDeviceController.js'
4
+
5
+ export default class SpyBleController extends BleDeviceController {
6
+ public infoLogs: string[] = []
7
+ public warnLogs: string[] = []
8
+ public errorLogs: string[] = []
9
+
10
+ public constructor(options: BleControllerOptions) {
11
+ super(options)
12
+ }
13
+
14
+ public getCharacteristicCallbacks() {
15
+ return this.charCallbacks
16
+ }
17
+
18
+ public getRssiIntervalMs() {
19
+ return this.rssiIntervalMs
20
+ }
21
+
22
+ public getConnected() {
23
+ return this.connected
24
+ }
25
+
26
+ public setLogInfoSpy() {
27
+ this.infoLogs = []
28
+
29
+ this.log.info = (...args: unknown[]) => {
30
+ const message = args.join(' ')
31
+ this.infoLogs.push(message)
32
+ return message
33
+ }
34
+ }
35
+
36
+ public setLogWarnSpy() {
37
+ this.warnLogs = []
38
+
39
+ this.log.warn = (...args: unknown[]) => {
40
+ const message = args.join(' ')
41
+ this.warnLogs.push(message)
42
+ return message
43
+ }
44
+ }
45
+
46
+ public setLogErrorSpy() {
47
+ this.errorLogs = []
48
+
49
+ this.log.error = (...args: unknown[]) => {
50
+ const message = args.join(' ')
51
+ this.errorLogs.push(message)
52
+ return message
53
+ }
54
+ }
55
+
56
+ public resetTestDouble() {
57
+ this.infoLogs = []
58
+ this.warnLogs = []
59
+ this.errorLogs = []
60
+ }
61
+ }
@@ -0,0 +1,46 @@
1
+ import {
2
+ EventMarkerEmitter,
3
+ EventMarkerOptions,
4
+ TimedEventMarker,
5
+ } from '../../impl/LslEventMarkerEmitter.js'
6
+
7
+ export default class FakeEventMarkerEmitter implements EventMarkerEmitter {
8
+ public static numCallsToConstructor = 0
9
+
10
+ public static callsToEmit: {
11
+ markerName: string
12
+ options?: EventMarkerOptions
13
+ }[] = []
14
+
15
+ public static callsToEmitMany: TimedEventMarker[][] = []
16
+ public static numCallsToInterrupt = 0
17
+ public static numCallsToDestroy = 0
18
+
19
+ public constructor() {
20
+ FakeEventMarkerEmitter.numCallsToConstructor++
21
+ }
22
+
23
+ public async emit(markerName: string, options?: EventMarkerOptions) {
24
+ FakeEventMarkerEmitter.callsToEmit.push({ markerName, options })
25
+ }
26
+
27
+ public async emitMany(markers: TimedEventMarker[]) {
28
+ FakeEventMarkerEmitter.callsToEmitMany.push(markers)
29
+ }
30
+
31
+ public interrupt() {
32
+ FakeEventMarkerEmitter.numCallsToInterrupt++
33
+ }
34
+
35
+ public destroy() {
36
+ FakeEventMarkerEmitter.numCallsToDestroy++
37
+ }
38
+
39
+ public static resetTestDouble() {
40
+ this.numCallsToConstructor = 0
41
+ this.callsToEmit = []
42
+ this.callsToEmitMany = []
43
+ this.numCallsToInterrupt = 0
44
+ this.numCallsToDestroy = 0
45
+ }
46
+ }
@@ -0,0 +1,31 @@
1
+ import LslEventMarkerEmitter from '../../impl/LslEventMarkerEmitter.js'
2
+ import { StreamOutlet } from '../../impl/LslStreamOutlet.js'
3
+
4
+ export default class SpyEventMarkerEmitter extends LslEventMarkerEmitter {
5
+ public static shouldCallWaitOnSuper = false
6
+
7
+ public totalwaitAfterMs: number
8
+
9
+ public constructor(outlet: StreamOutlet) {
10
+ super(outlet)
11
+
12
+ this.totalwaitAfterMs = 0
13
+ }
14
+
15
+ public async wait(waitAfterMs: number) {
16
+ this.totalwaitAfterMs += waitAfterMs
17
+
18
+ if (SpyEventMarkerEmitter.shouldCallWaitOnSuper) {
19
+ return super.wait(waitAfterMs)
20
+ }
21
+ return Promise.resolve()
22
+ }
23
+
24
+ public getStreamOutlet() {
25
+ return this.outlet
26
+ }
27
+
28
+ public resetTestDouble() {
29
+ this.totalwaitAfterMs = 0
30
+ }
31
+ }
@@ -0,0 +1,29 @@
1
+ import {
2
+ EventMarkerEmitter,
3
+ EventMarkerOptions,
4
+ TimedEventMarker,
5
+ } from '../../impl/LslEventMarkerEmitter.js'
6
+
7
+ export default class ThrowingEventMarkerEmitter implements EventMarkerEmitter {
8
+ private errorMsg = 'Intentional throwing: '
9
+
10
+ public constructor() {
11
+ throw new Error(this.errorMsg + 'constructor')
12
+ }
13
+
14
+ public async emit(_markerName: string, _options?: EventMarkerOptions) {
15
+ throw new Error(this.errorMsg + 'emit')
16
+ }
17
+
18
+ public async emitMany(_markers: TimedEventMarker[]) {
19
+ throw new Error(this.errorMsg + 'emitMany')
20
+ }
21
+
22
+ public interrupt() {
23
+ throw new Error(this.errorMsg + 'interrupt')
24
+ }
25
+
26
+ public destroy() {
27
+ throw new Error(this.errorMsg + 'destroy')
28
+ }
29
+ }
@@ -1,13 +1,13 @@
1
1
  import generateId from '@neurodevs/generate-id'
2
+ import { InfoHandle } from '@neurodevs/ndx-native'
2
3
 
3
- import { BoundStreamInfo } from 'impl/LiblslAdapter.js'
4
4
  import { StreamInfo, StreamInfoOptions } from '../../impl/LslStreamInfo.js'
5
5
 
6
6
  export default class FakeStreamInfo implements StreamInfo {
7
7
  public static callsToConstructor: (StreamInfoOptions | undefined)[] = []
8
8
  public static numCallsToDestroy = 0
9
9
 
10
- public static boundInfo = {} as BoundStreamInfo
10
+ public static infoHandle = {} as InfoHandle
11
11
 
12
12
  public name = generateId()
13
13
  public type = generateId()
@@ -29,8 +29,8 @@ export default class FakeStreamInfo implements StreamInfo {
29
29
  return this.channelNames.length
30
30
  }
31
31
 
32
- public get boundInfo() {
33
- return FakeStreamInfo.boundInfo
32
+ public get infoHandle() {
33
+ return FakeStreamInfo.infoHandle
34
34
  }
35
35
 
36
36
  public static resetTestDouble() {