@neurodevs/node-lsl 8.0.0 → 8.0.2

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 (52) hide show
  1. package/.vscode/settings.json +17 -20
  2. package/build/.spruce/errors/errors.types.js +0 -2
  3. package/build/.spruce/errors/errors.types.js.map +1 -1
  4. package/build/__tests__/behavioral/LslOutlet.test.js +9 -14
  5. package/build/__tests__/behavioral/LslOutlet.test.js.map +1 -1
  6. package/build/__tests__/behavioral/TimeMarkerOutlet.test.js.map +1 -1
  7. package/build/__tests__/implementation/Liblsl.test.d.ts +3 -1
  8. package/build/__tests__/implementation/Liblsl.test.js +44 -19
  9. package/build/__tests__/implementation/Liblsl.test.js.map +1 -1
  10. package/build/__tests__/support/consts.js.map +1 -1
  11. package/build/__tests__/support/generateRandomOutletOptions.js.map +1 -1
  12. package/build/assertions.js.map +1 -1
  13. package/build/consts.js.map +1 -1
  14. package/build/errors/SpruceError.d.ts +1 -1
  15. package/build/errors/SpruceError.js.map +1 -1
  16. package/build/errors/failedToLoadLiblsl.builder.js.map +1 -1
  17. package/build/implementations/Liblsl.js +40 -17
  18. package/build/implementations/Liblsl.js.map +1 -1
  19. package/build/implementations/LslOutlet.js +6 -14
  20. package/build/implementations/LslOutlet.js.map +1 -1
  21. package/build/implementations/TimeMarkerOutlet.js +4 -1
  22. package/build/implementations/TimeMarkerOutlet.js.map +1 -1
  23. package/build/nodeLsl.types.d.ts +11 -11
  24. package/build/testDoubles/FakeLiblsl.js.map +1 -1
  25. package/build/testDoubles/MockTimeMarkerOutlet.js.map +1 -1
  26. package/build/testDoubles/SpyExtendsTimeMarkerOutlet.js.map +1 -1
  27. package/build/testDoubles/SpyImplementsTimeMarkerOutlet.js.map +1 -1
  28. package/build/validations.js.map +1 -1
  29. package/eslint.config.mjs +3 -0
  30. package/package.json +26 -26
  31. package/src/.spruce/errors/errors.types.ts +0 -3
  32. package/src/__tests__/behavioral/LslOutlet.test.ts +272 -267
  33. package/src/__tests__/behavioral/TimeMarkerOutlet.test.ts +144 -141
  34. package/src/__tests__/implementation/Liblsl.test.ts +461 -424
  35. package/src/__tests__/support/consts.ts +22 -22
  36. package/src/__tests__/support/generateRandomOutletOptions.ts +13 -13
  37. package/src/assertions.ts +39 -39
  38. package/src/consts.ts +16 -16
  39. package/src/errors/SpruceError.ts +16 -16
  40. package/src/errors/failedToLoadLiblsl.builder.ts +8 -8
  41. package/src/implementations/Liblsl.ts +205 -181
  42. package/src/implementations/LslOutlet.ts +114 -114
  43. package/src/implementations/TimeMarkerOutlet.ts +50 -50
  44. package/src/nodeLsl.types.ts +65 -85
  45. package/src/testDoubles/FakeLiblsl.ts +58 -56
  46. package/src/testDoubles/MockTimeMarkerOutlet.ts +56 -56
  47. package/src/testDoubles/SpyExtendsTimeMarkerOutlet.ts +11 -11
  48. package/src/testDoubles/SpyImplementsTimeMarkerOutlet.ts +28 -28
  49. package/src/validations.ts +6 -6
  50. package/tsconfig.json +3 -4
  51. package/.eslintignore +0 -3
  52. package/.eslintrc.js +0 -3
@@ -2,188 +2,212 @@ import { assertOptions } from '@sprucelabs/schema'
2
2
  import { DataType, define, open } from 'ffi-rs'
3
3
  import SpruceError from '../errors/SpruceError'
4
4
  import {
5
- Liblsl,
6
- LiblslBindings,
7
- CreateStreamInfoOptions,
8
- AppendChannelsToStreamInfoOptions,
9
- CreateOutletOptions,
10
- DestroyOutletOptions,
11
- PushSampleFloatTimestampOptions,
12
- PushSampleStringTimestampOptions,
5
+ Liblsl,
6
+ LiblslBindings,
7
+ CreateStreamInfoOptions,
8
+ AppendChannelsToStreamInfoOptions,
9
+ CreateOutletOptions,
10
+ DestroyOutletOptions,
11
+ PushSampleFloatTimestampOptions,
12
+ PushSampleStringTimestampOptions,
13
13
  } from '../nodeLsl.types'
14
14
 
15
15
  export default class LiblslImpl implements Liblsl {
16
- private static instance?: Liblsl
17
- public static ffiRsOpen = open
18
- public static ffiRsDefine = define
19
-
20
- private bindings: LiblslBindings
21
-
22
- public static getInstance() {
23
- if (!this.instance) {
24
- this.setInstance(new this())
25
- }
26
- return this.instance!
27
- }
28
-
29
- public static setInstance(instance: Liblsl) {
30
- this.instance = instance
31
- }
32
-
33
- public static resetInstance() {
34
- delete this.instance
35
- }
36
-
37
- protected constructor() {
38
- const path = process.env.LIBLSL_PATH!
39
-
40
- if (!path) {
41
- assertOptions(process, ['env.LIBLSL_PATH'])
42
- }
43
-
44
- try {
45
- this.bindings = this.loadBindings(path)
46
- } catch {
47
- throw new SpruceError({
48
- code: 'FAILED_TO_LOAD_LIBLSL',
49
- liblslPath: path,
50
- })
51
- }
52
- }
53
-
54
- private loadBindings(liblslPath: string) {
55
- LiblslImpl.ffiRsOpen({
56
- library: 'lsl',
57
- path: liblslPath,
58
- })
59
-
60
- return LiblslImpl.ffiRsDefine({
61
- lsl_create_streaminfo: {
62
- library: 'lsl',
63
- retType: DataType.External,
64
- paramsType: [
65
- DataType.String,
66
- DataType.String,
67
- DataType.I32,
68
- DataType.Double,
69
- DataType.I32,
70
- DataType.String,
71
- ],
72
- },
73
- lsl_create_outlet: {
74
- library: 'lsl',
75
- retType: DataType.External,
76
- paramsType: [DataType.External, DataType.I32, DataType.I32],
77
- },
78
- lsl_destroy_outlet: {
79
- library: 'lsl',
80
- retType: DataType.Void,
81
- paramsType: [DataType.External],
82
- },
83
- lsl_local_clock: {
84
- library: 'lsl',
85
- retType: DataType.Double,
86
- paramsType: [],
87
- },
88
- lsl_push_sample_ft: {
89
- library: 'lsl',
90
- retType: DataType.Void,
91
- paramsType: [DataType.External, DataType.DoubleArray, DataType.Double],
92
- },
93
- lsl_push_sample_strt: {
94
- library: 'lsl',
95
- retType: DataType.Void,
96
- paramsType: [DataType.External, DataType.StringArray, DataType.Double],
97
- },
98
- lsl_get_desc: {
99
- library: 'lsl',
100
- retType: DataType.External,
101
- paramsType: [DataType.External],
102
- },
103
- lsl_append_child: {
104
- library: 'lsl',
105
- retType: DataType.External,
106
- paramsType: [DataType.External, DataType.String],
107
- },
108
- lsl_append_child_value: {
109
- library: 'lsl',
110
- retType: DataType.External,
111
- paramsType: [DataType.External, DataType.String, DataType.String],
112
- },
113
- }) as unknown as LiblslBindings
114
- }
115
-
116
- public createStreamInfo(options: CreateStreamInfoOptions) {
117
- const { name, type, channelCount, sampleRate, channelFormat, sourceId } =
118
- assertOptions(options, [
119
- 'name',
120
- 'type',
121
- 'channelCount',
122
- 'sampleRate',
123
- 'channelFormat',
124
- 'sourceId',
125
- ])
126
-
127
- return this.bindings.lsl_create_streaminfo(
128
- name,
129
- type,
130
- channelCount,
131
- sampleRate,
132
- channelFormat,
133
- sourceId
134
- )
135
- }
136
-
137
- public appendChannelsToStreamInfo(
138
- options: AppendChannelsToStreamInfoOptions
139
- ) {
140
- const { info, channels } = assertOptions(options, ['info', 'channels'])
141
-
142
- const description = this.bindings.lsl_get_desc(info)
143
- const parent = this.bindings.lsl_append_child(description, 'channels')
144
-
145
- for (const channel of channels) {
146
- const child = this.bindings.lsl_append_child(parent, 'channel')
147
- this.bindings.lsl_append_child_value(child, 'label', channel.label)
148
- this.bindings.lsl_append_child_value(child, 'unit', channel.unit)
149
- this.bindings.lsl_append_child_value(child, 'type', channel.type)
150
- }
151
- }
152
-
153
- public createOutlet(options: CreateOutletOptions) {
154
- const { info, chunkSize, maxBuffered } = assertOptions(options, [
155
- 'info',
156
- 'chunkSize',
157
- 'maxBuffered',
158
- ])
159
-
160
- return this.bindings.lsl_create_outlet(info, chunkSize, maxBuffered)
161
- }
162
-
163
- public destroyOutlet(options: DestroyOutletOptions) {
164
- const { outlet } = assertOptions(options, ['outlet'])
165
- this.bindings.lsl_destroy_outlet(outlet)
166
- }
167
-
168
- public pushSampleFloatTimestamp(options: PushSampleFloatTimestampOptions) {
169
- const { outlet, sample, timestamp } = assertOptions(options, [
170
- 'outlet',
171
- 'sample',
172
- 'timestamp',
173
- ])
174
- this.bindings.lsl_push_sample_ft(outlet, sample, timestamp)
175
- }
176
-
177
- public pushSampleStringTimestamp(options: PushSampleStringTimestampOptions) {
178
- const { outlet, sample, timestamp } = assertOptions(options, [
179
- 'outlet',
180
- 'sample',
181
- 'timestamp',
182
- ])
183
- this.bindings.lsl_push_sample_strt(outlet, sample, timestamp)
184
- }
185
-
186
- public localClock() {
187
- return this.bindings.lsl_local_clock()
188
- }
16
+ private static instance?: Liblsl
17
+ public static ffiRsOpen = open
18
+ public static ffiRsDefine = define
19
+
20
+ private bindings: LiblslBindings
21
+
22
+ public static getInstance() {
23
+ if (!this.instance) {
24
+ this.setInstance(new this())
25
+ }
26
+ return this.instance!
27
+ }
28
+
29
+ public static setInstance(instance: Liblsl) {
30
+ this.instance = instance
31
+ }
32
+
33
+ public static resetInstance() {
34
+ delete this.instance
35
+ }
36
+
37
+ protected constructor() {
38
+ const path = process.env.LIBLSL_PATH!
39
+
40
+ if (!path) {
41
+ assertOptions(process, ['env.LIBLSL_PATH'])
42
+ }
43
+
44
+ try {
45
+ this.bindings = this.loadBindings(path)
46
+ } catch {
47
+ throw new SpruceError({
48
+ code: 'FAILED_TO_LOAD_LIBLSL',
49
+ liblslPath: path,
50
+ })
51
+ }
52
+ }
53
+
54
+ private loadBindings(liblslPath: string) {
55
+ LiblslImpl.ffiRsOpen({
56
+ library: 'lsl',
57
+ path: liblslPath,
58
+ })
59
+
60
+ return LiblslImpl.ffiRsDefine({
61
+ lsl_create_streaminfo: {
62
+ library: 'lsl',
63
+ retType: DataType.External,
64
+ paramsType: [
65
+ DataType.String,
66
+ DataType.String,
67
+ DataType.I32,
68
+ DataType.Double,
69
+ DataType.I32,
70
+ DataType.String,
71
+ ],
72
+ },
73
+ lsl_create_outlet: {
74
+ library: 'lsl',
75
+ retType: DataType.External,
76
+ paramsType: [DataType.External, DataType.I32, DataType.I32],
77
+ },
78
+ lsl_destroy_outlet: {
79
+ library: 'lsl',
80
+ retType: DataType.Void,
81
+ paramsType: [DataType.External],
82
+ },
83
+ lsl_local_clock: {
84
+ library: 'lsl',
85
+ retType: DataType.Double,
86
+ paramsType: [],
87
+ },
88
+ lsl_push_sample_ft: {
89
+ library: 'lsl',
90
+ retType: DataType.Void,
91
+ paramsType: [
92
+ DataType.External,
93
+ DataType.FloatArray,
94
+ DataType.Double,
95
+ ],
96
+ },
97
+ lsl_push_sample_strt: {
98
+ library: 'lsl',
99
+ retType: DataType.Void,
100
+ paramsType: [
101
+ DataType.External,
102
+ DataType.StringArray,
103
+ DataType.Double,
104
+ ],
105
+ },
106
+ lsl_get_desc: {
107
+ library: 'lsl',
108
+ retType: DataType.External,
109
+ paramsType: [DataType.External],
110
+ },
111
+ lsl_append_child: {
112
+ library: 'lsl',
113
+ retType: DataType.External,
114
+ paramsType: [DataType.External, DataType.String],
115
+ },
116
+ lsl_append_child_value: {
117
+ library: 'lsl',
118
+ retType: DataType.External,
119
+ paramsType: [
120
+ DataType.External,
121
+ DataType.String,
122
+ DataType.String,
123
+ ],
124
+ },
125
+ }) as unknown as LiblslBindings
126
+ }
127
+
128
+ public createStreamInfo(options: CreateStreamInfoOptions) {
129
+ const {
130
+ name,
131
+ type,
132
+ channelCount,
133
+ sampleRate,
134
+ channelFormat,
135
+ sourceId,
136
+ } = assertOptions(options, [
137
+ 'name',
138
+ 'type',
139
+ 'channelCount',
140
+ 'sampleRate',
141
+ 'channelFormat',
142
+ 'sourceId',
143
+ ])
144
+
145
+ return this.bindings.lsl_create_streaminfo([
146
+ name,
147
+ type,
148
+ channelCount,
149
+ sampleRate,
150
+ channelFormat,
151
+ sourceId,
152
+ ])
153
+ }
154
+
155
+ public appendChannelsToStreamInfo(
156
+ options: AppendChannelsToStreamInfoOptions
157
+ ) {
158
+ const { info, channels } = assertOptions(options, ['info', 'channels'])
159
+
160
+ const description = this.bindings.lsl_get_desc([info])
161
+ const parent = this.bindings.lsl_append_child([description, 'channels'])
162
+
163
+ for (const channel of channels) {
164
+ const child = this.bindings.lsl_append_child([parent, 'channel'])
165
+ this.bindings.lsl_append_child_value([
166
+ child,
167
+ 'label',
168
+ channel.label,
169
+ ])
170
+ this.bindings.lsl_append_child_value([child, 'unit', channel.unit])
171
+ this.bindings.lsl_append_child_value([child, 'type', channel.type])
172
+ }
173
+ }
174
+
175
+ public createOutlet(options: CreateOutletOptions) {
176
+ const { info, chunkSize, maxBuffered } = assertOptions(options, [
177
+ 'info',
178
+ 'chunkSize',
179
+ 'maxBuffered',
180
+ ])
181
+
182
+ return this.bindings.lsl_create_outlet([info, chunkSize, maxBuffered])
183
+ }
184
+
185
+ public destroyOutlet(options: DestroyOutletOptions) {
186
+ const { outlet } = assertOptions(options, ['outlet'])
187
+ this.bindings.lsl_destroy_outlet([outlet])
188
+ }
189
+
190
+ public pushSampleFloatTimestamp(options: PushSampleFloatTimestampOptions) {
191
+ const { outlet, sample, timestamp } = assertOptions(options, [
192
+ 'outlet',
193
+ 'sample',
194
+ 'timestamp',
195
+ ])
196
+ this.bindings.lsl_push_sample_ft([outlet, sample, timestamp])
197
+ }
198
+
199
+ public pushSampleStringTimestamp(
200
+ options: PushSampleStringTimestampOptions
201
+ ) {
202
+ const { outlet, sample, timestamp } = assertOptions(options, [
203
+ 'outlet',
204
+ 'sample',
205
+ 'timestamp',
206
+ ])
207
+ this.bindings.lsl_push_sample_strt([outlet, sample, timestamp])
208
+ }
209
+
210
+ public localClock() {
211
+ return this.bindings.lsl_local_clock([])
212
+ }
189
213
  }
@@ -1,124 +1,124 @@
1
1
  import { assertOptions } from '@sprucelabs/schema'
2
2
  import {
3
- assertValidChannelCount,
4
- assertValidChannelFormat,
5
- assertValidChunkSize,
6
- assertValidMaxBuffered,
7
- assertValidSampleRate,
3
+ assertValidChannelCount,
4
+ assertValidChannelFormat,
5
+ assertValidChunkSize,
6
+ assertValidMaxBuffered,
7
+ assertValidSampleRate,
8
8
  } from '../assertions'
9
9
  import { CHANNEL_FORMATS_MAP } from '../consts'
10
10
  import {
11
- LslOutlet,
12
- LslOutletOptions,
13
- ChannelFormat,
14
- BoundOutlet,
15
- BoundStreamInfo,
16
- Liblsl,
17
- LslSample,
11
+ LslOutlet,
12
+ LslOutletOptions,
13
+ ChannelFormat,
14
+ BoundOutlet,
15
+ BoundStreamInfo,
16
+ Liblsl,
17
+ LslSample,
18
18
  } from '../nodeLsl.types'
19
19
  import LiblslImpl from './Liblsl'
20
20
 
21
21
  export default class LslOutletImpl implements LslOutlet {
22
- public static Class?: new (options: LslOutletOptions) => LslOutlet
23
- private options: LslOutletOptions
24
- private streamInfo: BoundStreamInfo
25
- private outlet: BoundOutlet
26
- private pushSampleByType: (options: any) => void
27
-
28
- public static Outlet(options: LslOutletOptions) {
29
- return new (this.Class ?? this)(options)
30
- }
31
-
32
- protected constructor(options: LslOutletOptions) {
33
- const { sampleRate, channelFormat } = assertOptions(options, [
34
- 'name',
35
- 'type',
36
- 'channelNames',
37
- 'sampleRate',
38
- 'channelFormat',
39
- 'sourceId',
40
- 'manufacturer',
41
- 'unit',
42
- 'chunkSize',
43
- 'maxBuffered',
44
- ])
45
-
46
- this.options = options
47
-
48
- const { chunkSize, maxBuffered, channelNames, ...streamInfoOptions } = this
49
- .options as any
50
-
51
- const channelCount = channelNames.length
52
-
53
- assertValidChannelCount(channelCount)
54
- assertValidSampleRate(sampleRate)
55
- assertValidChannelFormat(channelFormat)
56
- assertValidChunkSize(chunkSize)
57
- assertValidMaxBuffered(maxBuffered)
58
-
59
- delete streamInfoOptions.manufacturer
60
- delete streamInfoOptions.unit
61
-
62
- this.streamInfo = this.lsl.createStreamInfo({
63
- ...streamInfoOptions,
64
- channelCount,
65
- channelFormat: this.lookupChannelFormat(channelFormat),
66
- })
67
-
68
- this.lsl.appendChannelsToStreamInfo({
69
- info: this.streamInfo,
70
- channels: channelNames.map((label: string) => ({
71
- label,
72
- unit: this.options.unit,
73
- type: this.options.type,
74
- })),
75
- })
76
-
77
- this.outlet = this.lsl.createOutlet({
78
- info: this.streamInfo,
79
- chunkSize: this.options.chunkSize,
80
- maxBuffered: this.options.maxBuffered,
81
- })
82
-
83
- const pushMethod = this.getPushMethod()
84
- this.pushSampleByType = this.lsl[pushMethod].bind(this.lsl)
85
- }
86
-
87
- public destroy() {
88
- this.lsl.destroyOutlet({ outlet: this.outlet })
89
- }
90
-
91
- public pushSample(sample: LslSample) {
92
- const timestamp = this.lsl.localClock()
93
-
94
- this.pushSampleByType({
95
- outlet: this.outlet,
96
- sample,
97
- timestamp,
98
- })
99
- }
100
-
101
- private getPushMethod() {
102
- const channelFormat = this.options.channelFormat
103
-
104
- const methodMap: Record<string, keyof Liblsl> = {
105
- float32: 'pushSampleFloatTimestamp',
106
- string: 'pushSampleStringTimestamp',
107
- }
108
-
109
- if (!(channelFormat in methodMap)) {
110
- throw new Error(
111
- `This method currently does not support the ${channelFormat} type! Please implement it.`
112
- )
113
- }
114
- return methodMap[channelFormat]
115
- }
116
-
117
- private lookupChannelFormat(channelFormat: ChannelFormat) {
118
- return CHANNEL_FORMATS_MAP[channelFormat]
119
- }
120
-
121
- private get lsl() {
122
- return LiblslImpl.getInstance()
123
- }
22
+ public static Class?: new (options: LslOutletOptions) => LslOutlet
23
+ private options: LslOutletOptions
24
+ private streamInfo: BoundStreamInfo
25
+ private outlet: BoundOutlet
26
+ private pushSampleByType: (options: any) => void
27
+
28
+ public static Outlet(options: LslOutletOptions) {
29
+ return new (this.Class ?? this)(options)
30
+ }
31
+
32
+ protected constructor(options: LslOutletOptions) {
33
+ const { sampleRate, channelFormat } = assertOptions(options, [
34
+ 'name',
35
+ 'type',
36
+ 'channelNames',
37
+ 'sampleRate',
38
+ 'channelFormat',
39
+ 'sourceId',
40
+ 'manufacturer',
41
+ 'unit',
42
+ 'chunkSize',
43
+ 'maxBuffered',
44
+ ])
45
+
46
+ this.options = options
47
+
48
+ const { chunkSize, maxBuffered, channelNames, ...streamInfoOptions } =
49
+ this.options as any
50
+
51
+ const channelCount = channelNames.length
52
+
53
+ assertValidChannelCount(channelCount)
54
+ assertValidSampleRate(sampleRate)
55
+ assertValidChannelFormat(channelFormat)
56
+ assertValidChunkSize(chunkSize)
57
+ assertValidMaxBuffered(maxBuffered)
58
+
59
+ delete streamInfoOptions.manufacturer
60
+ delete streamInfoOptions.unit
61
+
62
+ this.streamInfo = this.lsl.createStreamInfo({
63
+ ...streamInfoOptions,
64
+ channelCount,
65
+ channelFormat: this.lookupChannelFormat(channelFormat),
66
+ })
67
+
68
+ this.lsl.appendChannelsToStreamInfo({
69
+ info: this.streamInfo,
70
+ channels: channelNames.map((label: string) => ({
71
+ label,
72
+ unit: this.options.unit,
73
+ type: this.options.type,
74
+ })),
75
+ })
76
+
77
+ this.outlet = this.lsl.createOutlet({
78
+ info: this.streamInfo,
79
+ chunkSize: this.options.chunkSize,
80
+ maxBuffered: this.options.maxBuffered,
81
+ })
82
+
83
+ const pushMethod = this.getPushMethod()
84
+ this.pushSampleByType = this.lsl[pushMethod].bind(this.lsl)
85
+ }
86
+
87
+ public destroy() {
88
+ this.lsl.destroyOutlet({ outlet: this.outlet })
89
+ }
90
+
91
+ public pushSample(sample: LslSample) {
92
+ const timestamp = this.lsl.localClock()
93
+
94
+ this.pushSampleByType({
95
+ outlet: this.outlet,
96
+ sample,
97
+ timestamp,
98
+ })
99
+ }
100
+
101
+ private getPushMethod() {
102
+ const channelFormat = this.options.channelFormat
103
+
104
+ const methodMap: Record<string, keyof Liblsl> = {
105
+ float32: 'pushSampleFloatTimestamp',
106
+ string: 'pushSampleStringTimestamp',
107
+ }
108
+
109
+ if (!(channelFormat in methodMap)) {
110
+ throw new Error(
111
+ `This method currently does not support the ${channelFormat} type! Please implement it.`
112
+ )
113
+ }
114
+ return methodMap[channelFormat]
115
+ }
116
+
117
+ private lookupChannelFormat(channelFormat: ChannelFormat) {
118
+ return CHANNEL_FORMATS_MAP[channelFormat]
119
+ }
120
+
121
+ private get lsl() {
122
+ return LiblslImpl.getInstance()
123
+ }
124
124
  }