@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,22 +1,19 @@
1
1
  import WebSocket, { WebSocketServer } from 'ws'
2
2
 
3
- import LslStreamInlet, {
4
- StreamInlet,
5
- StreamInletOptions,
6
- } from './LslStreamInlet.js'
3
+ import LslStreamInlet, { LslInlet, LslInletOptions } from './LslStreamInlet.js'
7
4
 
8
- export default class LslWebSocketBridge implements WebSocketBridge {
9
- public static Class?: WebSocketBridgeConstructor
5
+ export default class LslWebSocketBridge implements LslWsBridge {
6
+ public static Class?: LslWsBridgeConstructor
10
7
  public static WS = WebSocket
11
8
  public static WSS = WebSocketServer
12
9
 
13
- private inlet: StreamInlet
10
+ private inlet: LslInlet
14
11
  private localServer?: WebSocketServer
15
12
  private remoteSockets?: WebSocket[]
16
13
 
17
14
  private isDestroyed = false
18
15
 
19
- protected constructor(options: WebSocketBridgeConstructorOptions) {
16
+ protected constructor(options: LslWsBridgeConstructorOptions) {
20
17
  const { inlet, localServer, remoteSockets } = options
21
18
 
22
19
  this.inlet = inlet
@@ -26,9 +23,9 @@ export default class LslWebSocketBridge implements WebSocketBridge {
26
23
  this.throwIfNoLocalServerOrRemoteSockets()
27
24
  }
28
25
 
29
- public static async Create(options: WebSocketBridgeOptions) {
26
+ public static async Create(options: LslWsBridgeOptions) {
30
27
  const { listenPort, connectUrls, ...rest } = options
31
- const inletOptions: StreamInletOptions = rest
28
+ const inletOptions: LslInletOptions = rest
32
29
 
33
30
  const localServer = this.WebSocketServer(listenPort)
34
31
  const remoteSockets = this.createSocketsFrom(connectUrls)
@@ -155,7 +152,7 @@ export default class LslWebSocketBridge implements WebSocketBridge {
155
152
  }
156
153
 
157
154
  private static async LslStreamInlet(
158
- options: StreamInletOptions,
155
+ options: LslInletOptions,
159
156
  localServer?: WebSocketServer,
160
157
  remoteSockets?: WebSocket[]
161
158
  ) {
@@ -164,25 +161,25 @@ export default class LslWebSocketBridge implements WebSocketBridge {
164
161
  }
165
162
  }
166
163
 
167
- export interface WebSocketBridge {
164
+ export interface LslWsBridge {
168
165
  activate(): Promise<void>
169
166
  deactivate(): void
170
167
  destroy(): void
171
168
  }
172
169
 
173
- export type WebSocketBridgeConstructor = new (
174
- options: WebSocketBridgeConstructorOptions
175
- ) => WebSocketBridge
170
+ export type LslWsBridgeConstructor = new (
171
+ options: LslWsBridgeConstructorOptions
172
+ ) => LslWsBridge
176
173
 
177
- export interface WebSocketBridgeOptions {
174
+ export interface LslWsBridgeOptions {
178
175
  sourceId: string
179
176
  chunkSize: number
180
177
  listenPort?: number
181
178
  connectUrls?: string | string[]
182
179
  }
183
180
 
184
- export interface WebSocketBridgeConstructorOptions extends WebSocketBridgeOptions {
185
- inlet: StreamInlet
181
+ export interface LslWsBridgeConstructorOptions extends LslWsBridgeOptions {
182
+ inlet: LslInlet
186
183
  localServer?: WebSocketServer
187
184
  remoteSockets?: WebSocket[]
188
185
  }
@@ -0,0 +1,136 @@
1
+ export default class WindowedClockRegressor implements ClockRegressor {
2
+ public static Class?: ClockRegressorConstructor
3
+
4
+ private readonly nominalHz: number
5
+ private readonly windowLenSec: number
6
+ private readonly defaultWindowLenSec = 30
7
+ private readonly history: {
8
+ deviceTime: number
9
+ earliestLslTime: number
10
+ }[] = []
11
+
12
+ private slope = 1 // lsl-host-seconds per device-second
13
+
14
+ protected constructor(
15
+ nominalHz: number,
16
+ options?: WindowedClockRegressorOptions
17
+ ) {
18
+ const { windowLenSec = this.defaultWindowLenSec } = options ?? {}
19
+
20
+ this.nominalHz = nominalHz
21
+ this.windowLenSec = windowLenSec
22
+ }
23
+
24
+ public static Create(
25
+ nominalHz: number,
26
+ options?: WindowedClockRegressorOptions
27
+ ) {
28
+ return new (this.Class ?? this)(nominalHz, options)
29
+ }
30
+
31
+ public deriveTimestamps(
32
+ deviceTime: number,
33
+ earliestLslTime: number,
34
+ chunkSize: number
35
+ ) {
36
+ this.throwIfDeviceTimeDoesNotIncrease(deviceTime)
37
+
38
+ this.history.push({ deviceTime, earliestLslTime })
39
+ this.pruneHistoryOutsideWindow()
40
+ this.refitSlope()
41
+
42
+ const anchor = this.anchorTimeFor(deviceTime)
43
+
44
+ const timestamps = Array(chunkSize).fill(0)
45
+
46
+ for (let i = 0; i <= chunkSize - 1; i++) {
47
+ const stepsFromLast = chunkSize - 1 - i
48
+ const deviceTimeOffset = -stepsFromLast / this.nominalHz
49
+ const lslTimeOffset = deviceTimeOffset * this.slope
50
+
51
+ timestamps[i] = anchor + lslTimeOffset
52
+ }
53
+
54
+ return timestamps
55
+ }
56
+
57
+ private anchorTimeFor(deviceTime: number) {
58
+ const xMean = this.currentMeanDeviceTime
59
+ const yMean = this.currentMeanLslTime
60
+
61
+ return yMean + this.slope * (deviceTime - xMean)
62
+ }
63
+
64
+ private throwIfDeviceTimeDoesNotIncrease(deviceTime: number) {
65
+ const last = this.history[this.history.length - 1]
66
+
67
+ if (last && deviceTime <= last.deviceTime) {
68
+ throw new Error(
69
+ `\nDevice time (${deviceTime}) did not increase from the previous device time (${last.deviceTime})! Device time is expected to always increase between calls, so this likely indicates a bug in the calling code.\n`
70
+ )
71
+ }
72
+ }
73
+
74
+ private pruneHistoryOutsideWindow() {
75
+ const { deviceTime: latest } = this.history[this.history.length - 1]
76
+ const cutoff = latest - this.windowLenSec
77
+
78
+ while (this.history.length > 0 && this.history[0].deviceTime < cutoff) {
79
+ this.history.shift()
80
+ }
81
+ }
82
+
83
+ private refitSlope() {
84
+ if (this.history.length < 2) {
85
+ return
86
+ }
87
+
88
+ const xMean = this.currentMeanDeviceTime
89
+ const yMean = this.currentMeanLslTime
90
+
91
+ let numerator = 0
92
+ let denominator = 0
93
+
94
+ for (const { deviceTime, earliestLslTime } of this.history) {
95
+ const xCentered = deviceTime - xMean
96
+
97
+ numerator += xCentered * (earliestLslTime - yMean)
98
+ denominator += xCentered * xCentered
99
+ }
100
+
101
+ this.slope = numerator / denominator
102
+ }
103
+
104
+ private get currentMeanDeviceTime() {
105
+ const totalDeviceTime = this.history.reduce(
106
+ (sum, { deviceTime }) => sum + deviceTime,
107
+ 0
108
+ )
109
+ return totalDeviceTime / this.history.length
110
+ }
111
+
112
+ private get currentMeanLslTime() {
113
+ const totalLslTime = this.history.reduce(
114
+ (sum, { earliestLslTime }) => sum + earliestLslTime,
115
+ 0
116
+ )
117
+ return totalLslTime / this.history.length
118
+ }
119
+ }
120
+
121
+ export interface ClockRegressor {
122
+ deriveTimestamps(
123
+ deviceTime: number,
124
+ earliestLslTime: number,
125
+ chunkSize: number
126
+ ): number[]
127
+ }
128
+
129
+ export type ClockRegressorConstructor = new (
130
+ nominalHz: number,
131
+ options?: WindowedClockRegressorOptions
132
+ ) => ClockRegressor
133
+
134
+ export interface WindowedClockRegressorOptions {
135
+ windowLenSec?: number
136
+ }
@@ -44,7 +44,7 @@ export default class BleDeviceController implements BleController {
44
44
  this.rssiIntervalMs = rssiIntervalMs
45
45
  }
46
46
 
47
- public static async Create(options: BleControllerOptions) {
47
+ public static Create(options: BleControllerOptions) {
48
48
  return new (this.Class ?? this)(options)
49
49
  }
50
50
 
@@ -165,6 +165,19 @@ export default class BleDeviceController implements BleController {
165
165
  this.throwIfError(status, error)
166
166
  }
167
167
 
168
+ public async subscribeCharacteristics(
169
+ charCallbacks: CharacteristicCallbacks
170
+ ) {
171
+ const { status, error } = this.ndx.addBleCharCallbacks({
172
+ deviceUuid: this.uuid,
173
+ charCallbacks,
174
+ })
175
+
176
+ this.throwIfError(status, error)
177
+
178
+ this.charCallbacks = [...this.charCallbacks, ...charCallbacks]
179
+ }
180
+
168
181
  public async disconnect() {
169
182
  const { status, error } = this.ndx.stopBleBackend({
170
183
  deviceUuid: this.uuid,
@@ -193,6 +206,10 @@ export interface BleController {
193
206
 
194
207
  connect(): Promise<void>
195
208
 
209
+ subscribeCharacteristics(
210
+ charCallbacks: CharacteristicCallbacks
211
+ ): Promise<void>
212
+
196
213
  writeCharacteristic(
197
214
  charUuid: CharacteristicUuid,
198
215
  value: string
@@ -0,0 +1,65 @@
1
+ import { LibndxAdapter } from '@neurodevs/ndx-native'
2
+
3
+ export default class UsbDeviceController implements UsbController {
4
+ public static Class?: UsbControllerConstructor
5
+
6
+ private onData: (data: Buffer, length: number, timestampSec: number) => void
7
+ private serialNumber: string
8
+
9
+ private ndx = LibndxAdapter.getInstance()
10
+
11
+ protected constructor(options: UsbControllerOptions) {
12
+ const { onData, serialNumber } = options ?? {}
13
+
14
+ this.onData = onData
15
+ this.serialNumber = serialNumber ?? ''
16
+ }
17
+
18
+ public static Create(options: UsbControllerOptions) {
19
+ return new (this.Class ?? this)(options)
20
+ }
21
+
22
+ public async connect() {
23
+ this.ndx.createUsbBackend(this.usbControllerOptions)
24
+ this.ndx.startUsbBackend(this.startUsbControllerOptions)
25
+ }
26
+
27
+ private get usbControllerOptions() {
28
+ return {
29
+ serialNumber: this.serialNumber,
30
+ }
31
+ }
32
+
33
+ private get startUsbControllerOptions() {
34
+ return {
35
+ ...this.usbControllerOptions,
36
+ onData: this.onData,
37
+ }
38
+ }
39
+
40
+ public async writeUsb(value: string) {
41
+ this.ndx.writeUsbBackend({
42
+ ...this.usbControllerOptions,
43
+ value,
44
+ })
45
+ }
46
+
47
+ public async disconnect() {
48
+ this.ndx.stopUsbBackend(this.usbControllerOptions)
49
+ }
50
+ }
51
+
52
+ export interface UsbController {
53
+ connect(): Promise<void>
54
+ writeUsb(value: string): Promise<void>
55
+ disconnect(): Promise<void>
56
+ }
57
+
58
+ export interface UsbControllerOptions {
59
+ onData: (data: Buffer, length: number, timestampSec: number) => void
60
+ serialNumber?: string
61
+ }
62
+
63
+ export type UsbControllerConstructor = new (
64
+ options?: UsbControllerOptions
65
+ ) => UsbController
package/src/index.ts CHANGED
@@ -1,66 +1,86 @@
1
+ // ClockRegressor
2
+
3
+ export { default as WindowedClockRegressor } from './impl/clocks/WindowedClockRegressor.js'
4
+ export * from './impl/clocks/WindowedClockRegressor.js'
5
+
6
+ export { default as FakeClockRegressor } from './testDoubles/ClockRegressor/FakeClockRegressor.js'
7
+ export * from './testDoubles/ClockRegressor/FakeClockRegressor.js'
8
+
1
9
  // DeviceController
2
10
 
3
11
  export { default as BleDeviceController } from './impl/controllers/BleDeviceController.js'
4
12
  export * from './impl/controllers/BleDeviceController.js'
5
13
 
14
+ export { default as SpyBleController } from './testDoubles/BleController/SpyBleController.js'
15
+ export * from './testDoubles/BleController/SpyBleController.js'
16
+
6
17
  export { default as FakeBleController } from './testDoubles/BleController/FakeBleController.js'
7
18
  export * from './testDoubles/BleController/FakeBleController.js'
8
19
 
9
- export { default as SpyBleController } from './testDoubles/BleController/SpyBleController.js'
10
- export * from './testDoubles/BleController/SpyBleController.js'
20
+ // LslWsBridge
11
21
 
12
- // EventMarkerEmitter
22
+ export { default as LslWebSocketBridge } from './impl/LslWebSocketBridge.js'
23
+ export * from './impl/LslWebSocketBridge.js'
24
+
25
+ export { default as SpyLslWsBridge } from './testDoubles/LslWsBridge/SpyLslWsBridge.js'
26
+ export * from './testDoubles/LslWsBridge/SpyLslWsBridge.js'
27
+
28
+ export { default as FakeLslWsBridge } from './testDoubles/LslWsBridge/FakeLslWsBridge.js'
29
+ export * from './testDoubles/LslWsBridge/FakeLslWsBridge.js'
30
+
31
+ // LslEmitter
13
32
 
14
33
  export { default as LslEventMarkerEmitter } from './impl/LslEventMarkerEmitter.js'
15
34
  export * from './impl/LslEventMarkerEmitter.js'
16
35
 
17
- export { default as FakeEventMarkerEmitter } from './testDoubles/EventMarkerEmitter/FakeEventMarkerEmitter.js'
18
- export * from './testDoubles/EventMarkerEmitter/FakeEventMarkerEmitter.js'
36
+ export { default as SpyLslEmitter } from './testDoubles/LslEmitter/SpyLslEmitter.js'
37
+ export * from './testDoubles/LslEmitter/SpyLslEmitter.js'
19
38
 
20
- export { default as SpyEventMarkerEmitter } from './testDoubles/EventMarkerEmitter/SpyEventMarkerEmitter.js'
21
- export * from './testDoubles/EventMarkerEmitter/SpyEventMarkerEmitter.js'
39
+ export { default as FakeLslEmitter } from './testDoubles/LslEmitter/FakeLslEmitter.js'
40
+ export * from './testDoubles/LslEmitter/FakeLslEmitter.js'
22
41
 
23
- export { default as ThrowingEventMarkerEmitter } from './testDoubles/EventMarkerEmitter/ThrowingEventMarkerEmitter.js'
24
- export * from './testDoubles/EventMarkerEmitter/ThrowingEventMarkerEmitter.js'
42
+ export { default as ThrowingLslEmitter } from './testDoubles/LslEmitter/ThrowingLslEmitter.js'
43
+ export * from './testDoubles/LslEmitter/ThrowingLslEmitter.js'
25
44
 
26
- // StreamInfo
45
+ // LslInfo
27
46
 
28
47
  export { default as LslStreamInfo } from './impl/LslStreamInfo.js'
29
48
  export * from './impl/LslStreamInfo.js'
30
49
 
31
- export { default as SpyStreamInfo } from './testDoubles/StreamInfo/SpyStreamInfo.js'
32
- export * from './testDoubles/StreamInfo/SpyStreamInfo.js'
50
+ export { default as SpyLslInfo } from './testDoubles/LslInfo/SpyLslInfo.js'
51
+ export * from './testDoubles/LslInfo/SpyLslInfo.js'
33
52
 
34
- export { default as FakeStreamInfo } from './testDoubles/StreamInfo/FakeStreamInfo.js'
35
- export * from './testDoubles/StreamInfo/FakeStreamInfo.js'
53
+ export { default as FakeLslInfo } from './testDoubles/LslInfo/FakeLslInfo.js'
54
+ export * from './testDoubles/LslInfo/FakeLslInfo.js'
36
55
 
37
- // StreamInlet
56
+ // LslInlet
38
57
 
39
58
  export { default as LslStreamInlet } from './impl/LslStreamInlet.js'
40
59
  export * from './impl/LslStreamInlet.js'
41
60
 
42
- export { default as FakeStreamInlet } from './testDoubles/StreamInlet/FakeStreamInlet.js'
43
- export * from './testDoubles/StreamInlet/FakeStreamInlet.js'
61
+ export { default as SpyLslInlet } from './testDoubles/LslInlet/SpyLslInlet.js'
62
+ export * from './testDoubles/LslInlet/SpyLslInlet.js'
44
63
 
45
- // StreamOutlet
64
+ export { default as FakeLslInlet } from './testDoubles/LslInlet/FakeLslInlet.js'
65
+ export * from './testDoubles/LslInlet/FakeLslInlet.js'
66
+
67
+ // LslOutlet
46
68
 
47
69
  export { default as LslStreamOutlet } from './impl/LslStreamOutlet.js'
48
70
  export * from './impl/LslStreamOutlet.js'
49
71
 
50
- export { default as FakeStreamOutlet } from './testDoubles/StreamOutlet/FakeStreamOutlet.js'
51
- export * from './testDoubles/StreamOutlet/FakeStreamOutlet.js'
52
-
53
- // WebSocketBridge
72
+ export { default as FakeLslOutlet } from './testDoubles/LslOutlet/FakeLslOutlet.js'
73
+ export * from './testDoubles/LslOutlet/FakeLslOutlet.js'
54
74
 
55
- export { default as LslWebSocketBridge } from './impl/LslWebSocketBridge.js'
56
- export * from './impl/LslWebSocketBridge.js'
75
+ export { default as generateRandomOutletOptions } from './testDoubles/generateRandomOutletOptions.js'
57
76
 
58
- export { default as FakeWebSocketBridge } from './testDoubles/WebSocketBridge/FakeWebSocketBridge.js'
59
- export * from './testDoubles/WebSocketBridge/FakeWebSocketBridge.js'
77
+ // UsbController
60
78
 
61
- // Utils
79
+ export { default as UsbDeviceController } from './impl/controllers/UsbDeviceController.js'
80
+ export * from './impl/controllers/UsbDeviceController.js'
62
81
 
63
- export { default as generateRandomOutletOptions } from './testDoubles/generateRandomOutletOptions.js'
82
+ export { default as FakeUsbController } from './testDoubles/UsbController/FakeUsbController.js'
83
+ export * from './testDoubles/UsbController/FakeUsbController.js'
64
84
 
65
85
  // WebSockets
66
86
 
@@ -1,6 +1,6 @@
1
1
  import LslEventMarkerEmitter from '../impl/LslEventMarkerEmitter.js'
2
2
 
3
- const outlet = await LslEventMarkerEmitter.Create()
3
+ const emitter = await LslEventMarkerEmitter.Create()
4
4
 
5
5
  const markers = [
6
6
  { name: 'phase-1-begin', waitAfterMs: 100 },
@@ -8,8 +8,9 @@ const markers = [
8
8
  { name: 'never-reached-marker', waitAfterMs: 100 },
9
9
  ]
10
10
 
11
- void outlet.emitMany(markers)
11
+ void emitter.emitMany(markers)
12
12
 
13
13
  await new Promise((resolve) => setTimeout(resolve, 250))
14
14
 
15
- outlet.interrupt()
15
+ emitter.interrupt()
16
+ emitter.destroy()
@@ -32,7 +32,7 @@ const charCallbacks = Object.entries(MUSE_CHARACTERISTIC_UUIDS).map(
32
32
  }
33
33
  )
34
34
 
35
- const muse = await BleDeviceController.Create({
35
+ const muse = BleDeviceController.Create({
36
36
  deviceUuid: 'CA6A61B7-B7A8-AF24-3C9E-04A6A5012554',
37
37
  charCallbacks,
38
38
  rssiIntervalMs: 1000,
@@ -0,0 +1,20 @@
1
+ import UsbDeviceController from '../impl/controllers/UsbDeviceController.js'
2
+
3
+ console.info('\nCreating UsbDeviceController...')
4
+
5
+ const controller = UsbDeviceController.Create({
6
+ serialNumber: 'DP04WG8J',
7
+ onData: (_data: Buffer, length: number, timestampSec: number) => {
8
+ console.info(`[${timestampSec}] Buffer length: ${length}`)
9
+ },
10
+ })
11
+
12
+ console.info('Connecting to UsbDeviceController...')
13
+
14
+ await controller.connect()
15
+
16
+ console.info('Waiting for 10 seconds...')
17
+
18
+ await new Promise((resolve) => setTimeout(resolve, 10000))
19
+
20
+ console.info('Done!')
@@ -2,6 +2,7 @@ import generateId from '@neurodevs/generate-id'
2
2
  import {
3
3
  BleController,
4
4
  BleControllerOptions,
5
+ CharacteristicCallbacks,
5
6
  } from '../../impl/controllers/BleDeviceController.js'
6
7
 
7
8
  export default class FakeBleController implements BleController {
@@ -14,6 +15,9 @@ export default class FakeBleController implements BleController {
14
15
  value: string
15
16
  }[] = []
16
17
 
18
+ public static callsToSubscribeCharacteristics: CharacteristicCallbacks[] =
19
+ []
20
+
17
21
  public static numCallsToDisconnect = 0
18
22
 
19
23
  public static fakeCharacteristics: Record<string, unknown> = {}
@@ -45,6 +49,12 @@ export default class FakeBleController implements BleController {
45
49
  })
46
50
  }
47
51
 
52
+ public async subscribeCharacteristics(
53
+ charCallbacks: CharacteristicCallbacks
54
+ ) {
55
+ FakeBleController.callsToSubscribeCharacteristics.push(charCallbacks)
56
+ }
57
+
48
58
  public async disconnect() {
49
59
  FakeBleController.numCallsToDisconnect++
50
60
  }
@@ -61,6 +71,7 @@ export default class FakeBleController implements BleController {
61
71
  this.callsToConstructor = []
62
72
  this.numCallsToConnect = 0
63
73
  this.callsToWriteCharacteristic = []
74
+ this.callsToSubscribeCharacteristics = []
64
75
  this.numCallsToDisconnect = 0
65
76
  }
66
77
  }
@@ -0,0 +1,46 @@
1
+ import {
2
+ ClockRegressor,
3
+ WindowedClockRegressorOptions,
4
+ } from '../../impl/clocks/WindowedClockRegressor.js'
5
+
6
+ export default class FakeClockRegressor implements ClockRegressor {
7
+ public static callsToConstructor: {
8
+ nominalHz: number
9
+ options?: WindowedClockRegressorOptions
10
+ }[] = []
11
+ public static callsToDeriveTimestamps: {
12
+ deviceTime: number
13
+ earliestLslTime: number
14
+ chunkSize: number
15
+ }[] = []
16
+
17
+ public static fakeResultValue = 0
18
+
19
+ public readonly nominalHz: number
20
+
21
+ public constructor(
22
+ nominalHz: number,
23
+ options?: WindowedClockRegressorOptions
24
+ ) {
25
+ FakeClockRegressor.callsToConstructor.push({ nominalHz, options })
26
+ this.nominalHz = nominalHz
27
+ }
28
+
29
+ public deriveTimestamps(
30
+ deviceTime: number,
31
+ earliestLslTime: number,
32
+ chunkSize: number
33
+ ) {
34
+ FakeClockRegressor.callsToDeriveTimestamps.push({
35
+ deviceTime,
36
+ earliestLslTime,
37
+ chunkSize,
38
+ })
39
+ return Array(chunkSize).fill(FakeClockRegressor.fakeResultValue)
40
+ }
41
+
42
+ public static resetTestDouble() {
43
+ FakeClockRegressor.callsToConstructor = []
44
+ FakeClockRegressor.callsToDeriveTimestamps = []
45
+ }
46
+ }
@@ -1,15 +1,15 @@
1
1
  import {
2
- EventMarkerEmitter,
3
- EventMarkerOptions,
2
+ LslEmitter,
3
+ EmitOptions,
4
4
  TimedEventMarker,
5
5
  } from '../../impl/LslEventMarkerEmitter.js'
6
6
 
7
- export default class FakeEventMarkerEmitter implements EventMarkerEmitter {
7
+ export default class FakeLslEmitter implements LslEmitter {
8
8
  public static numCallsToConstructor = 0
9
9
 
10
10
  public static callsToEmit: {
11
11
  markerName: string
12
- options?: EventMarkerOptions
12
+ options?: EmitOptions
13
13
  }[] = []
14
14
 
15
15
  public static callsToEmitMany: TimedEventMarker[][] = []
@@ -17,23 +17,23 @@ export default class FakeEventMarkerEmitter implements EventMarkerEmitter {
17
17
  public static numCallsToDestroy = 0
18
18
 
19
19
  public constructor() {
20
- FakeEventMarkerEmitter.numCallsToConstructor++
20
+ FakeLslEmitter.numCallsToConstructor++
21
21
  }
22
22
 
23
- public async emit(markerName: string, options?: EventMarkerOptions) {
24
- FakeEventMarkerEmitter.callsToEmit.push({ markerName, options })
23
+ public async emit(markerName: string, options?: EmitOptions) {
24
+ FakeLslEmitter.callsToEmit.push({ markerName, options })
25
25
  }
26
26
 
27
27
  public async emitMany(markers: TimedEventMarker[]) {
28
- FakeEventMarkerEmitter.callsToEmitMany.push(markers)
28
+ FakeLslEmitter.callsToEmitMany.push(markers)
29
29
  }
30
30
 
31
31
  public interrupt() {
32
- FakeEventMarkerEmitter.numCallsToInterrupt++
32
+ FakeLslEmitter.numCallsToInterrupt++
33
33
  }
34
34
 
35
35
  public destroy() {
36
- FakeEventMarkerEmitter.numCallsToDestroy++
36
+ FakeLslEmitter.numCallsToDestroy++
37
37
  }
38
38
 
39
39
  public static resetTestDouble() {
@@ -1,12 +1,12 @@
1
1
  import LslEventMarkerEmitter from '../../impl/LslEventMarkerEmitter.js'
2
- import { StreamOutlet } from '../../impl/LslStreamOutlet.js'
2
+ import { LslOutlet } from '../../impl/LslStreamOutlet.js'
3
3
 
4
- export default class SpyEventMarkerEmitter extends LslEventMarkerEmitter {
4
+ export default class SpyLslEmitter extends LslEventMarkerEmitter {
5
5
  public static shouldCallWaitOnSuper = false
6
6
 
7
7
  public totalwaitAfterMs: number
8
8
 
9
- public constructor(outlet: StreamOutlet) {
9
+ public constructor(outlet: LslOutlet) {
10
10
  super(outlet)
11
11
 
12
12
  this.totalwaitAfterMs = 0
@@ -15,7 +15,7 @@ export default class SpyEventMarkerEmitter extends LslEventMarkerEmitter {
15
15
  public async wait(waitAfterMs: number) {
16
16
  this.totalwaitAfterMs += waitAfterMs
17
17
 
18
- if (SpyEventMarkerEmitter.shouldCallWaitOnSuper) {
18
+ if (SpyLslEmitter.shouldCallWaitOnSuper) {
19
19
  return super.wait(waitAfterMs)
20
20
  }
21
21
  return Promise.resolve()
@@ -1,17 +1,17 @@
1
1
  import {
2
- EventMarkerEmitter,
3
- EventMarkerOptions,
2
+ LslEmitter,
3
+ EmitOptions,
4
4
  TimedEventMarker,
5
5
  } from '../../impl/LslEventMarkerEmitter.js'
6
6
 
7
- export default class ThrowingEventMarkerEmitter implements EventMarkerEmitter {
7
+ export default class ThrowingLslEmitter implements LslEmitter {
8
8
  private errorMsg = 'Intentional throwing: '
9
9
 
10
10
  public constructor() {
11
11
  throw new Error(this.errorMsg + 'constructor')
12
12
  }
13
13
 
14
- public async emit(_markerName: string, _options?: EventMarkerOptions) {
14
+ public async emit(_markerName: string, _options?: EmitOptions) {
15
15
  throw new Error(this.errorMsg + 'emit')
16
16
  }
17
17