@neurodevs/node-lsl 3.3.1 → 4.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.
@@ -90,21 +90,55 @@ export default class LslOutletTest extends AbstractSpruceTest {
90
90
  }
91
91
  }
92
92
 
93
- @test('pushing [1, 2] sample sends to LSL', [1, 2])
94
- @test('pushing [1] sample sends to LSL', [1])
95
- protected static async canPushSampleToLsl(sample: LslSample) {
96
- this.pushSample(sample)
93
+ @test('pushing [1, 2] sample sends to LSL', [1.0, 2.0])
94
+ @test('pushing [1] sample sends to LSL', [1.0])
95
+ protected static async canPushFloatSampleToLsl(sample: LslSample) {
96
+ const outlet = this.FloatOutlet()
97
+ outlet.pushSample(sample)
97
98
 
98
99
  assert.isEqual(
99
- this.spyLiblsl.lastPushSampleOptions?.outlet,
100
+ this.spyLiblsl.lastPushSampleFtOptions?.outlet,
100
101
  this.spyLiblsl.outlet
101
102
  )
102
- assert.isEqualDeep(this.spyLiblsl.lastPushSampleOptions?.sample, sample)
103
+ assert.isEqualDeep(this.spyLiblsl.lastPushSampleFtOptions?.sample, sample)
103
104
  assert.isEqualDeep(this.spyLiblsl.lastCreateOutletOptions, {
104
105
  info: this.spyLiblsl.streamInfo,
105
106
  chunkSize: this.randomOutletOptions.chunkSize,
106
107
  maxBuffered: this.randomOutletOptions.maxBuffered,
107
108
  })
109
+ assert.isNumber(this.spyLiblsl.lastPushSampleFtOptions?.timestamp)
110
+ }
111
+
112
+ @test()
113
+ protected static async canPushStringSampleToLsl() {
114
+ const sample = [generateId()]
115
+ const outlet = this.StringOutlet()
116
+
117
+ outlet.pushSample(sample)
118
+
119
+ assert.isEqual(
120
+ this.spyLiblsl.lastPushSampleStrtOptions?.outlet,
121
+ this.spyLiblsl.outlet
122
+ )
123
+ assert.isEqualDeep(this.spyLiblsl.lastPushSampleStrtOptions?.sample, sample)
124
+ assert.isNumber(this.spyLiblsl.lastPushSampleStrtOptions?.timestamp)
125
+ }
126
+
127
+ @test()
128
+ protected static async pushingStringTwiceGivesDifferentTimestamps() {
129
+ const outlet = this.StringOutlet()
130
+ const sample = [generateId()]
131
+
132
+ outlet.pushSample(sample)
133
+ const t1 = this.spyLiblsl.lastPushSampleStrtOptions?.timestamp
134
+
135
+ await this.wait(10)
136
+
137
+ outlet.pushSample(sample)
138
+ const t2 = this.spyLiblsl.lastPushSampleStrtOptions?.timestamp
139
+
140
+ assert.isNotEqual(t1, t2)
141
+ assert.isEqual(this.spyLiblsl.localClockHitCount, 2)
108
142
  }
109
143
 
110
144
  @test()
@@ -163,17 +197,32 @@ export default class LslOutletTest extends AbstractSpruceTest {
163
197
 
164
198
  @test()
165
199
  protected static async pushSampleShouldNotCreateMultipleOutlets() {
166
- const outlet = this.Outlet()
167
- outlet.pushSample([1])
168
- outlet.pushSample([2])
169
- outlet.pushSample([3])
200
+ const outlet = this.FloatOutlet()
201
+ outlet.pushSample([1.0])
202
+ outlet.pushSample([2.0])
203
+ outlet.pushSample([3.0])
170
204
 
171
205
  assert.isEqual(this.spyLiblsl.createStreamInfoHitCount, 1)
172
206
  }
173
207
 
174
- private static pushSample(sample: LslSample) {
175
- const outlet = this.Outlet()
176
- outlet.pushSample(sample)
208
+ @test()
209
+ protected static async pushSampleThrowsWithUnsupportedType() {
210
+ const unsupportedTypes = [
211
+ 'undefined',
212
+ 'double64',
213
+ 'int32',
214
+ 'int16',
215
+ 'int8',
216
+ 'int64',
217
+ ] as const
218
+
219
+ for (let unsupportedType of unsupportedTypes) {
220
+ const outlet = this.Outlet({ channelFormat: unsupportedType })
221
+ assert.doesThrow(
222
+ () => outlet.pushSample([]),
223
+ `This method currently does not support the ${unsupportedType} type! Please implement it.`
224
+ )
225
+ }
177
226
  }
178
227
 
179
228
  private static assertThrowsInvalidMaxBuffered(maxBuffered: number) {
@@ -211,6 +260,14 @@ export default class LslOutletTest extends AbstractSpruceTest {
211
260
  ...options,
212
261
  })
213
262
  }
263
+
264
+ private static StringOutlet() {
265
+ return this.Outlet({ channelFormat: 'string' })
266
+ }
267
+
268
+ private static FloatOutlet() {
269
+ return this.Outlet({ channelFormat: 'float32' })
270
+ }
214
271
  }
215
272
 
216
273
  class CheckingOutlet extends LslOutlet {
@@ -1,5 +1,9 @@
1
1
  import { randomInt } from 'crypto'
2
- import AbstractSpruceTest, { assert, test } from '@sprucelabs/test-utils'
2
+ import AbstractSpruceTest, {
3
+ assert,
4
+ generateId,
5
+ test,
6
+ } from '@sprucelabs/test-utils'
3
7
  import LiblslImpl from '../../Liblsl'
4
8
  import { LslOutletOptions } from '../../LslOutlet'
5
9
  import TimeMarkerOutlet from '../../TimeMarkerOutlet'
@@ -8,18 +12,19 @@ import { SpyLiblsl } from '../support/SpyLiblsl'
8
12
 
9
13
  export default class TimeMarkerOutletTest extends AbstractSpruceTest {
10
14
  private static spyLiblsl: SpyLiblsl
15
+ private static outlet: SpyTimeMarkerOutlet
11
16
 
12
17
  protected static async beforeEach() {
13
18
  await super.beforeEach()
14
19
  TimeMarkerOutlet.Class = SpyTimeMarkerOutlet
15
20
  this.spyLiblsl = new SpyLiblsl()
16
21
  LiblslImpl.setInstance(this.spyLiblsl)
22
+ this.outlet = this.Outlet()
17
23
  }
18
24
 
19
25
  @test()
20
26
  protected static async loadsWithTimeMarkerSpecificOptions() {
21
- const outlet = this.Outlet()
22
- assert.isEqualDeep(outlet.options, {
27
+ assert.isEqualDeep(this.outlet.spyOptions, {
23
28
  name: 'Time markers',
24
29
  type: 'Markers',
25
30
  channelNames: ['Markers'],
@@ -37,7 +42,33 @@ export default class TimeMarkerOutletTest extends AbstractSpruceTest {
37
42
  protected static async canOverrideDefaultOptions() {
38
43
  const options = generateRandomOutletOptions(randomInt(8))
39
44
  const outlet = this.Outlet(options)
40
- assert.isEqualDeep(outlet.options, options)
45
+ assert.isEqualDeep(outlet.spyOptions, options)
46
+ }
47
+
48
+ @test()
49
+ protected static async pushingSingleMarkerIncrementsHitCountAndWaitTime() {
50
+ const markers = [this.generateRandomDurationMarker()]
51
+ await this.outlet.pushMarkers(markers)
52
+ assert.isEqual(this.spyLiblsl.pushSampleStrtHitCount, 1)
53
+ assert.isEqual(this.outlet.totalWaitTimeMs, markers[0].durationMs)
54
+ }
55
+
56
+ @test()
57
+ protected static async pushingTwoMarkersIncrementsHitCountAndWaitTimeTwice() {
58
+ const markers = [
59
+ this.generateRandomDurationMarker(),
60
+ this.generateRandomDurationMarker(),
61
+ ]
62
+ await this.outlet.pushMarkers(markers)
63
+ assert.isEqual(this.spyLiblsl.pushSampleStrtHitCount, 2)
64
+ assert.isEqual(
65
+ this.outlet.totalWaitTimeMs,
66
+ markers[0].durationMs + markers[1].durationMs
67
+ )
68
+ }
69
+
70
+ private static generateRandomDurationMarker() {
71
+ return { name: generateId(), durationMs: randomInt(100, 1000) }
41
72
  }
42
73
 
43
74
  private static Outlet(options?: Partial<LslOutletOptions>) {
@@ -46,9 +77,17 @@ export default class TimeMarkerOutletTest extends AbstractSpruceTest {
46
77
  }
47
78
 
48
79
  class SpyTimeMarkerOutlet extends TimeMarkerOutlet {
49
- public options: LslOutletOptions
80
+ public spyOptions: LslOutletOptions
81
+ public totalWaitTimeMs: number
82
+
50
83
  public constructor(options: LslOutletOptions) {
51
84
  super(options)
52
- this.options = options
85
+ this.spyOptions = options
86
+ this.totalWaitTimeMs = 0
87
+ }
88
+
89
+ public async wait(durationMs: number) {
90
+ this.totalWaitTimeMs += durationMs
91
+ return Promise.resolve()
53
92
  }
54
93
  }
@@ -10,17 +10,13 @@ import ref from 'ref-napi'
10
10
  import LiblslImpl, {
11
11
  Liblsl,
12
12
  LiblslBindings,
13
- CreateStreamInfoOptions,
14
- AppendChannelsToStreamInfoOptions,
15
- CreateOutletOptions,
16
- DestroyOutletOptions,
17
- PushSampleOptions,
18
13
  LslChannel,
19
14
  BoundOutlet,
20
15
  BoundStreamInfo,
21
16
  BoundDesc,
22
17
  BoundChild,
23
18
  } from '../../Liblsl'
19
+ import { SpyLiblsl } from '../support/SpyLiblsl'
24
20
 
25
21
  export default class LiblslTest extends AbstractSpruceTest {
26
22
  private static lsl: Liblsl
@@ -35,9 +31,9 @@ export default class LiblslTest extends AbstractSpruceTest {
35
31
  private static appendChildParams: any[] = []
36
32
  private static createOutletParams?: any[]
37
33
  private static destroyOutletParams?: any[]
38
- private static pushSampleParams?: any[]
34
+ private static pushSampleFtParams?: any[]
35
+ private static pushSampleStrtParams?: any[]
39
36
  private static appendChildValueParams: any[]
40
- private static localClock: number
41
37
  private static shouldThrowWhenCreatingBindings: boolean
42
38
  private static getDescriptionParams?: [BoundStreamInfo]
43
39
  private static fakeChildNamedChannel: BoundChild
@@ -51,7 +47,7 @@ export default class LiblslTest extends AbstractSpruceTest {
51
47
  delete this.createStreamInfoParams
52
48
  delete this.createOutletParams
53
49
  delete this.destroyOutletParams
54
- delete this.pushSampleParams
50
+ delete this.pushSampleFtParams
55
51
  delete this.getDescriptionParams
56
52
  this.appendChildParams = []
57
53
  this.appendChildValueParams = []
@@ -63,54 +59,19 @@ export default class LiblslTest extends AbstractSpruceTest {
63
59
  this.fakeOutlet = {}
64
60
  this.fakeChildNamedChannels = {}
65
61
  this.fakeChildNamedChannel = {}
62
+ this.fakeBindings = this.FakeBindings()
66
63
 
67
- this.localClock = new Date().getTime()
68
64
  this.shouldThrowWhenCreatingBindings = false
69
65
 
70
66
  this.appendChildHitCount = 0
71
67
 
72
- this.fakeBindings = {
73
- lsl_create_streaminfo: (...params: any[]) => {
74
- this.createStreamInfoParams = params
75
- return this.fakeStreamInfo
76
- },
77
- lsl_create_outlet: (...params: any[]) => {
78
- this.createOutletParams = params
79
- return this.fakeOutlet
80
- },
81
- lsl_destroy_outlet: (...params: any[]) => {
82
- this.destroyOutletParams = params
83
- return
84
- },
85
- lsl_push_sample_ft: (...params: any[]) => {
86
- this.pushSampleParams = params
87
- return 0
88
- },
89
- lsl_local_clock: () => this.localClock,
90
- lsl_get_desc: (info: BoundStreamInfo) => {
91
- this.getDescriptionParams = [info]
92
- return this.fakeDesc
93
- },
94
- lsl_append_child: (...params: any) => {
95
- this.appendChildParams.push(params)
96
- if (this.appendChildHitCount === 0) {
97
- this.appendChildHitCount++
98
- return this.fakeChildNamedChannels
99
- }
100
- return this.fakeChildNamedChannel
101
- },
102
- lsl_append_child_value: (...params: any[]) => {
103
- this.appendChildValueParams.push(params)
104
- },
105
- }
106
-
107
68
  LiblslImpl.ffi = {
108
69
  //@ts-ignore
109
70
  Library: (path: string, options: Record<string, any>) => {
110
71
  this.libraryPath = path
111
72
  this.libraryOptions = options
112
73
  if (this.shouldThrowWhenCreatingBindings) {
113
- throw new Error('Failed to create bindings')
74
+ throw new Error('Failed to create bindings!')
114
75
  }
115
76
  return this.fakeBindings
116
77
  },
@@ -173,11 +134,20 @@ export default class LiblslTest extends AbstractSpruceTest {
173
134
  }
174
135
 
175
136
  @test()
176
- protected static async throwsWhenPushSampleIsMissingRequiredParams() {
137
+ protected static async throwsWhenPushSampleFtIsMissingRequiredParams() {
177
138
  //@ts-ignore
178
- const err = assert.doesThrow(() => this.lsl.pushSample())
139
+ const err = assert.doesThrow(() => this.lsl.pushSampleFt())
179
140
  errorAssert.assertError(err, 'MISSING_PARAMETERS', {
180
- parameters: ['outlet', 'sample'],
141
+ parameters: ['outlet', 'sample', 'timestamp'],
142
+ })
143
+ }
144
+
145
+ @test()
146
+ protected static async throwsWhenPushSampleStrtIsMissingRequiredParams() {
147
+ //@ts-ignore
148
+ const err = assert.doesThrow(() => this.lsl.pushSampleFt())
149
+ errorAssert.assertError(err, 'MISSING_PARAMETERS', {
150
+ parameters: ['outlet', 'sample', 'timestamp'],
181
151
  })
182
152
  }
183
153
 
@@ -194,7 +164,7 @@ export default class LiblslTest extends AbstractSpruceTest {
194
164
 
195
165
  @test()
196
166
  protected static canSetInstance() {
197
- const fake = new FakeLiblsl()
167
+ const fake = new SpyLiblsl()
198
168
  LiblslImpl.setInstance(fake)
199
169
  assert.isEqual(LiblslImpl.getInstance(), fake)
200
170
  }
@@ -213,6 +183,7 @@ export default class LiblslTest extends AbstractSpruceTest {
213
183
  lsl_destroy_outlet: ['void', [outletType]],
214
184
  lsl_local_clock: ['double', []],
215
185
  lsl_push_sample_ft: ['void', [outletType, FloatArray, 'double']],
186
+ lsl_push_sample_strt: ['void', [outletType, StringArray, 'double']],
216
187
  lsl_get_desc: [xmlPtr, [streamInfo]],
217
188
  lsl_append_child: [xmlPtr, [xmlPtr, 'string']],
218
189
  lsl_append_child_value: [xmlPtr, [xmlPtr, 'string', 'string']],
@@ -240,16 +211,33 @@ export default class LiblslTest extends AbstractSpruceTest {
240
211
  }
241
212
 
242
213
  @test()
243
- protected static async canPushSampleWithRequiredParams() {
244
- const expected = [1, 2, 3]
214
+ protected static async canPushFloatSample() {
215
+ const expected = [1.0, 2.0, 3.0]
216
+ const timestamp = randomInt(100)
245
217
  const options = {
246
218
  outlet: this.fakeOutlet,
247
219
  sample: expected,
220
+ timestamp,
248
221
  }
249
- this.lsl.pushSample(options)
250
- assert.isEqual(this.pushSampleParams?.[0], this.fakeOutlet)
251
- assert.isEqual(this.pushSampleParams?.[1], expected)
252
- assert.isEqual(this.pushSampleParams?.[2], this.localClock)
222
+ this.lsl.pushSampleFt(options)
223
+ assert.isEqual(this.pushSampleFtParams?.[0], this.fakeOutlet)
224
+ assert.isEqual(this.pushSampleFtParams?.[1], expected)
225
+ assert.isEqual(this.pushSampleFtParams?.[2], timestamp)
226
+ }
227
+
228
+ @test()
229
+ protected static async canPushStringSample() {
230
+ const expected = [generateId()]
231
+ const timestamp = randomInt(100)
232
+ const options = {
233
+ outlet: this.fakeOutlet,
234
+ sample: expected,
235
+ timestamp,
236
+ }
237
+ this.lsl.pushSampleStrt(options)
238
+ assert.isEqual(this.pushSampleStrtParams?.[0], this.fakeOutlet)
239
+ assert.isEqualDeep(this.pushSampleStrtParams?.[1], expected)
240
+ assert.isEqual(this.pushSampleStrtParams?.[2], timestamp)
253
241
  }
254
242
 
255
243
  @test()
@@ -313,6 +301,14 @@ export default class LiblslTest extends AbstractSpruceTest {
313
301
  assert.isEqualDeep(this.destroyOutletParams, Object.values(options))
314
302
  }
315
303
 
304
+ @test()
305
+ protected static async callingLocalClockTwiceReturnsDifferentTimestamps() {
306
+ const t1 = this.lsl.localClock()
307
+ await this.wait(10)
308
+ const t2 = this.lsl.localClock()
309
+ assert.isNotEqual(t1, t2)
310
+ }
311
+
316
312
  private static createRandomStreamInfo() {
317
313
  return this.lsl.createStreamInfo(
318
314
  this.generateRandomCreateStreamInfoOptions()
@@ -348,27 +344,48 @@ export default class LiblslTest extends AbstractSpruceTest {
348
344
  sourceId: generateId(),
349
345
  }
350
346
  }
351
- }
352
347
 
353
- class FakeLiblsl implements Liblsl {
354
- public createStreamInfo(_options: CreateStreamInfoOptions): BoundStreamInfo {
355
- return {} as BoundStreamInfo
356
- }
357
-
358
- public appendChannelsToStreamInfo(
359
- _options: AppendChannelsToStreamInfoOptions
360
- ): void {}
361
-
362
- public createOutlet(_options: CreateOutletOptions): BoundOutlet {
363
- return {} as BoundOutlet
348
+ private static FakeBindings(): LiblslBindings {
349
+ return {
350
+ lsl_create_streaminfo: (...params: any[]) => {
351
+ this.createStreamInfoParams = params
352
+ return this.fakeStreamInfo
353
+ },
354
+ lsl_create_outlet: (...params: any[]) => {
355
+ this.createOutletParams = params
356
+ return this.fakeOutlet
357
+ },
358
+ lsl_destroy_outlet: (...params: any[]) => {
359
+ this.destroyOutletParams = params
360
+ },
361
+ lsl_push_sample_ft: (...params: any[]) => {
362
+ this.pushSampleFtParams = params
363
+ },
364
+ lsl_push_sample_strt: (...params: any[]) => {
365
+ this.pushSampleStrtParams = params
366
+ },
367
+ lsl_local_clock: () => new Date().getTime(),
368
+ lsl_get_desc: (info: BoundStreamInfo) => {
369
+ this.getDescriptionParams = [info]
370
+ return this.fakeDesc
371
+ },
372
+ lsl_append_child: (...params: any) => {
373
+ this.appendChildParams.push(params)
374
+ if (this.appendChildHitCount === 0) {
375
+ this.appendChildHitCount++
376
+ return this.fakeChildNamedChannels
377
+ }
378
+ return this.fakeChildNamedChannel
379
+ },
380
+ lsl_append_child_value: (...params: any[]) => {
381
+ this.appendChildValueParams.push(params)
382
+ },
383
+ }
364
384
  }
365
-
366
- public destroyOutlet(_options: DestroyOutletOptions): void {}
367
-
368
- public pushSample(_options: PushSampleOptions): void {}
369
385
  }
370
386
 
371
387
  const streamInfo = ref.refType(ref.types.void)
372
388
  const outletType = ref.refType(ref.types.void)
373
389
  const FloatArray = ArrayType(ref.types.float)
390
+ const StringArray = ArrayType(ref.types.CString)
374
391
  const xmlPtr = ref.refType(ref.types.void)
@@ -1,23 +1,28 @@
1
1
  import {
2
- CreateOutletOptions,
3
- CreateStreamInfoOptions,
4
2
  Liblsl,
5
3
  BoundOutlet,
6
4
  BoundStreamInfo,
5
+ CreateStreamInfoOptions,
7
6
  AppendChannelsToStreamInfoOptions,
8
- PushSampleOptions,
7
+ CreateOutletOptions,
8
+ PushSampleFtOptions as PushSampleFtOptions,
9
+ PushSampleStrtOptions,
9
10
  } from '../../Liblsl'
10
11
 
11
12
  export class SpyLiblsl implements Liblsl {
13
+ public outlet: BoundOutlet = {} as BoundOutlet
14
+ public streamInfo: BoundStreamInfo = {} as BoundStreamInfo
15
+
12
16
  public lastCreateStreamInfoOptions?: CreateStreamInfoOptions
13
17
  public lastAppendChannelsToStreamInfoOptions?: AppendChannelsToStreamInfoOptions
14
18
  public lastCreateOutletOptions?: CreateOutletOptions
15
- public lastPushSampleOptions?: PushSampleOptions
19
+ public lastPushSampleFtOptions?: PushSampleFtOptions
20
+ public lastPushSampleStrtOptions?: PushSampleStrtOptions
16
21
 
17
- public outlet: BoundOutlet = {} as BoundOutlet
18
- public streamInfo: BoundStreamInfo = {} as BoundStreamInfo
19
22
  public createStreamInfoHitCount = 0
20
23
  public destroyOutletHitCount = 0
24
+ public localClockHitCount = 0
25
+ public pushSampleStrtHitCount = 0
21
26
 
22
27
  public createStreamInfo(options: CreateStreamInfoOptions): BoundStreamInfo {
23
28
  this.createStreamInfoHitCount++
@@ -27,7 +32,7 @@ export class SpyLiblsl implements Liblsl {
27
32
 
28
33
  public appendChannelsToStreamInfo(
29
34
  options: AppendChannelsToStreamInfoOptions
30
- ): void {
35
+ ) {
31
36
  this.lastAppendChannelsToStreamInfoOptions = options
32
37
  }
33
38
 
@@ -40,7 +45,17 @@ export class SpyLiblsl implements Liblsl {
40
45
  this.destroyOutletHitCount++
41
46
  }
42
47
 
43
- public pushSample(options: PushSampleOptions): void {
44
- this.lastPushSampleOptions = options
48
+ public pushSampleFt(options: PushSampleFtOptions) {
49
+ this.lastPushSampleFtOptions = options
50
+ }
51
+
52
+ public pushSampleStrt(options: PushSampleStrtOptions) {
53
+ this.lastPushSampleStrtOptions = options
54
+ this.pushSampleStrtHitCount++
55
+ }
56
+
57
+ public localClock() {
58
+ this.localClockHitCount++
59
+ return new Date().getTime()
45
60
  }
46
61
  }