@neurodevs/node-lsl 2.0.2 → 3.2.1
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.
- package/README.md +1 -1
- package/build/Liblsl.d.ts +33 -25
- package/build/Liblsl.js +29 -30
- package/build/Liblsl.js.map +1 -1
- package/build/LslOutlet.d.ts +8 -7
- package/build/LslOutlet.js +28 -21
- package/build/LslOutlet.js.map +1 -1
- package/build/TimeMarkerOutlet.d.ts +4 -0
- package/build/TimeMarkerOutlet.js +26 -0
- package/build/TimeMarkerOutlet.js.map +1 -0
- package/build/__tests__/behavioral/LslOutlet.test.d.ts +10 -12
- package/build/__tests__/behavioral/LslOutlet.test.js +47 -98
- package/build/__tests__/behavioral/LslOutlet.test.js.map +1 -1
- package/build/__tests__/behavioral/TimeMarkerOutlet.test.d.ts +8 -0
- package/build/__tests__/behavioral/TimeMarkerOutlet.test.js +85 -0
- package/build/__tests__/behavioral/TimeMarkerOutlet.test.js.map +1 -0
- package/build/__tests__/implementation/Liblsl.test.d.ts +9 -9
- package/build/__tests__/implementation/Liblsl.test.js +76 -66
- package/build/__tests__/implementation/Liblsl.test.js.map +1 -1
- package/build/__tests__/support/SpyLiblsl.d.ts +14 -0
- package/build/__tests__/support/SpyLiblsl.js +27 -0
- package/build/__tests__/support/SpyLiblsl.js.map +1 -0
- package/build/__tests__/support/consts.d.ts +2 -0
- package/build/__tests__/support/consts.js +14 -0
- package/build/__tests__/support/consts.js.map +1 -0
- package/build/__tests__/support/generateRandomOutletOptions.d.ts +2 -0
- package/build/__tests__/support/generateRandomOutletOptions.js +21 -0
- package/build/__tests__/support/generateRandomOutletOptions.js.map +1 -0
- package/build/errors/SpruceError.js.map +1 -1
- package/build/index.d.ts +1 -0
- package/build/index.js +3 -1
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/Liblsl.ts +76 -73
- package/src/LslOutlet.ts +39 -37
- package/src/TimeMarkerOutlet.ts +19 -0
- package/src/__tests__/behavioral/LslOutlet.test.ts +50 -119
- package/src/__tests__/behavioral/TimeMarkerOutlet.test.ts +54 -0
- package/src/__tests__/implementation/Liblsl.test.ts +96 -84
- package/src/__tests__/support/SpyLiblsl.ts +41 -0
- package/src/__tests__/support/consts.ts +12 -0
- package/src/__tests__/support/generateRandomOutletOptions.ts +19 -0
- package/src/index.ts +2 -0
package/src/Liblsl.ts
CHANGED
|
@@ -11,51 +11,35 @@ export default class LiblslImpl implements Liblsl {
|
|
|
11
11
|
|
|
12
12
|
public static getInstance() {
|
|
13
13
|
if (!this.instance) {
|
|
14
|
-
this.
|
|
14
|
+
this.setInstance(new this())
|
|
15
15
|
}
|
|
16
16
|
return this.instance!
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
public static reset() {
|
|
20
|
-
delete this.instance
|
|
21
|
-
}
|
|
22
|
-
|
|
23
19
|
public static setInstance(instance: Liblsl) {
|
|
24
20
|
this.instance = instance
|
|
25
21
|
}
|
|
26
22
|
|
|
23
|
+
public static resetInstance() {
|
|
24
|
+
delete this.instance
|
|
25
|
+
}
|
|
26
|
+
|
|
27
27
|
public constructor() {
|
|
28
28
|
const path = process.env.LIBLSL_PATH!
|
|
29
29
|
|
|
30
30
|
if (!path) {
|
|
31
31
|
assertOptions(process, ['env.LIBLSL_PATH'])
|
|
32
32
|
}
|
|
33
|
+
|
|
33
34
|
try {
|
|
34
35
|
this.bindings = this.loadBindings(path)
|
|
35
|
-
} catch
|
|
36
|
-
console.error(err)
|
|
36
|
+
} catch {
|
|
37
37
|
throw new SpruceError({
|
|
38
38
|
code: 'FAILED_TO_LOAD_LIBLSL',
|
|
39
39
|
liblslPath: path,
|
|
40
40
|
})
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
|
-
public appendChannelsToStreamInfo(
|
|
44
|
-
info: LslBindingsStreamInfo,
|
|
45
|
-
channels: LslChannel[]
|
|
46
|
-
): void {
|
|
47
|
-
assertOptions({ info, channels }, ['info', 'channels'])
|
|
48
|
-
const desc = this.bindings.lsl_get_desc(info)
|
|
49
|
-
|
|
50
|
-
const parent = this.bindings.lsl_append_child(desc, 'channels')
|
|
51
|
-
|
|
52
|
-
for (const channel of channels) {
|
|
53
|
-
const c = this.bindings.lsl_append_child(parent, 'channel')
|
|
54
|
-
this.bindings.lsl_append_child_value(c, 'label', channel.label)
|
|
55
|
-
this.bindings.lsl_append_child_value(c, 'unit', channel.unit)
|
|
56
|
-
this.bindings.lsl_append_child_value(c, 'type', channel.type)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
43
|
|
|
60
44
|
private loadBindings(path: string) {
|
|
61
45
|
return LiblslImpl.ffi.Library(path!, {
|
|
@@ -69,28 +53,10 @@ export default class LiblslImpl implements Liblsl {
|
|
|
69
53
|
lsl_get_desc: [xmlPtr, [streamInfo]],
|
|
70
54
|
lsl_append_child: [xmlPtr, [xmlPtr, 'string']],
|
|
71
55
|
lsl_append_child_value: [xmlPtr, [xmlPtr, 'string', 'string']],
|
|
72
|
-
}) as
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
public pushSample(outlet: LslBindingsOutlet, sample: LslSample): void {
|
|
76
|
-
assertOptions({ outlet, sample }, ['outlet', 'sample'])
|
|
77
|
-
const clock = this.bindings.lsl_local_clock()
|
|
78
|
-
this.bindings.lsl_push_sample_ft(outlet, sample, clock)
|
|
56
|
+
}) as LiblslBindings
|
|
79
57
|
}
|
|
80
58
|
|
|
81
|
-
public
|
|
82
|
-
const { info, chunkSize, maxBuffered } = assertOptions(options, [
|
|
83
|
-
'info',
|
|
84
|
-
'chunkSize',
|
|
85
|
-
'maxBuffered',
|
|
86
|
-
])
|
|
87
|
-
|
|
88
|
-
return this.bindings.lsl_create_outlet(info, chunkSize, maxBuffered)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
public createStreamInfo(
|
|
92
|
-
options: CreateStreamInfoOptions
|
|
93
|
-
): LslBindingsStreamInfo {
|
|
59
|
+
public createStreamInfo(options: CreateStreamInfoOptions): BoundStreamInfo {
|
|
94
60
|
const { name, type, channelCount, sampleRate, channelFormat, sourceId } =
|
|
95
61
|
assertOptions(options, [
|
|
96
62
|
'name',
|
|
@@ -110,20 +76,49 @@ export default class LiblslImpl implements Liblsl {
|
|
|
110
76
|
sourceId
|
|
111
77
|
)
|
|
112
78
|
}
|
|
79
|
+
|
|
80
|
+
public appendChannelsToStreamInfo(
|
|
81
|
+
options: AppendChannelsToStreamInfoOptions
|
|
82
|
+
): void {
|
|
83
|
+
const { info, channels } = assertOptions(options, ['info', 'channels'])
|
|
84
|
+
|
|
85
|
+
const desc = this.bindings.lsl_get_desc(info)
|
|
86
|
+
|
|
87
|
+
const parent = this.bindings.lsl_append_child(desc, 'channels')
|
|
88
|
+
|
|
89
|
+
for (const channel of channels) {
|
|
90
|
+
const child = this.bindings.lsl_append_child(parent, 'channel')
|
|
91
|
+
this.bindings.lsl_append_child_value(child, 'label', channel.label)
|
|
92
|
+
this.bindings.lsl_append_child_value(child, 'unit', channel.unit)
|
|
93
|
+
this.bindings.lsl_append_child_value(child, 'type', channel.type)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public createOutlet(options: CreateOutletOptions) {
|
|
98
|
+
const { info, chunkSize, maxBuffered } = assertOptions(options, [
|
|
99
|
+
'info',
|
|
100
|
+
'chunkSize',
|
|
101
|
+
'maxBuffered',
|
|
102
|
+
])
|
|
103
|
+
|
|
104
|
+
return this.bindings.lsl_create_outlet(info, chunkSize, maxBuffered)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
public pushSample(options: PushSampleOptions): void {
|
|
108
|
+
const { outlet, sample } = assertOptions(options, ['outlet', 'sample'])
|
|
109
|
+
|
|
110
|
+
const clock = this.bindings.lsl_local_clock()
|
|
111
|
+
this.bindings.lsl_push_sample_ft(outlet, sample, clock)
|
|
112
|
+
}
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
export interface Liblsl {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
): void
|
|
120
|
-
createStreamInfo(options: CreateStreamInfoOptions): LslBindingsStreamInfo
|
|
121
|
-
createOutlet(options: CreateOutletOptions): LslBindingsOutlet
|
|
122
|
-
pushSample(outlet: LslBindingsOutlet, sample: LslSample): void
|
|
116
|
+
createStreamInfo(options: CreateStreamInfoOptions): BoundStreamInfo
|
|
117
|
+
appendChannelsToStreamInfo(options: AppendChannelsToStreamInfoOptions): void
|
|
118
|
+
createOutlet(options: CreateOutletOptions): BoundOutlet
|
|
119
|
+
pushSample(options: PushSampleOptions): void
|
|
123
120
|
}
|
|
124
121
|
|
|
125
|
-
export type LslSample = (number | string | undefined)[]
|
|
126
|
-
|
|
127
122
|
export interface CreateStreamInfoOptions {
|
|
128
123
|
name: string
|
|
129
124
|
type: string
|
|
@@ -135,11 +130,28 @@ export interface CreateStreamInfoOptions {
|
|
|
135
130
|
unit?: string
|
|
136
131
|
}
|
|
137
132
|
|
|
133
|
+
export interface AppendChannelsToStreamInfoOptions {
|
|
134
|
+
info: BoundStreamInfo
|
|
135
|
+
channels: LslChannel[]
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface CreateOutletOptions {
|
|
139
|
+
info: BoundStreamInfo
|
|
140
|
+
chunkSize: number
|
|
141
|
+
maxBuffered: number
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface PushSampleOptions {
|
|
145
|
+
outlet: BoundOutlet
|
|
146
|
+
sample: LslSample
|
|
147
|
+
}
|
|
148
|
+
|
|
138
149
|
export interface LslChannel {
|
|
139
150
|
label: string
|
|
140
151
|
unit: string
|
|
141
152
|
type: string
|
|
142
153
|
}
|
|
154
|
+
export type LslSample = (number | string | undefined)[]
|
|
143
155
|
|
|
144
156
|
export interface LiblslBindings {
|
|
145
157
|
lsl_create_streaminfo(
|
|
@@ -149,41 +161,32 @@ export interface LiblslBindings {
|
|
|
149
161
|
sampleRate: number,
|
|
150
162
|
channelFormat: number,
|
|
151
163
|
sourceId: string
|
|
152
|
-
):
|
|
164
|
+
): BoundStreamInfo
|
|
153
165
|
|
|
154
166
|
lsl_create_outlet(
|
|
155
|
-
info:
|
|
167
|
+
info: BoundStreamInfo,
|
|
156
168
|
chunkSize: number,
|
|
157
169
|
maxBuffered: number
|
|
158
|
-
):
|
|
170
|
+
): BoundOutlet
|
|
159
171
|
|
|
160
172
|
lsl_push_sample_ft(
|
|
161
|
-
outlet:
|
|
173
|
+
outlet: BoundOutlet,
|
|
162
174
|
sample: LslSample,
|
|
163
175
|
timestamp: number
|
|
164
176
|
): void
|
|
165
177
|
|
|
166
178
|
lsl_local_clock(): number
|
|
167
|
-
lsl_get_desc(info:
|
|
168
|
-
lsl_append_child(desc:
|
|
169
|
-
lsl_append_child_value(
|
|
170
|
-
child: LslBindingsChild,
|
|
171
|
-
name: string,
|
|
172
|
-
value: string
|
|
173
|
-
): void
|
|
179
|
+
lsl_get_desc(info: BoundStreamInfo): BoundDesc
|
|
180
|
+
lsl_append_child(desc: BoundDesc, name: string): BoundChild
|
|
181
|
+
lsl_append_child_value(child: BoundChild, name: string, value: string): void
|
|
174
182
|
}
|
|
175
183
|
|
|
184
|
+
export interface BoundStreamInfo {}
|
|
185
|
+
export interface BoundOutlet {}
|
|
186
|
+
export interface BoundDesc {}
|
|
187
|
+
export interface BoundChild {}
|
|
188
|
+
|
|
176
189
|
const streamInfo = ref.refType(ref.types.void)
|
|
177
190
|
const outletType = ref.refType(ref.types.void)
|
|
178
191
|
const FloatArray = ArrayType(ref.types.float)
|
|
179
192
|
const xmlPtr = ref.refType(ref.types.void)
|
|
180
|
-
export interface CreateOutletOptions {
|
|
181
|
-
info: LslBindingsStreamInfo
|
|
182
|
-
chunkSize: number
|
|
183
|
-
maxBuffered: number
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
export interface LslBindingsStreamInfo {}
|
|
187
|
-
export interface LslBindingsOutlet {}
|
|
188
|
-
export interface LslBindingsDesc {}
|
|
189
|
-
export interface LslBindingsChild {}
|
package/src/LslOutlet.ts
CHANGED
|
@@ -1,21 +1,17 @@
|
|
|
1
1
|
import { SchemaError, assertOptions } from '@sprucelabs/schema'
|
|
2
|
-
import LiblslImpl, {
|
|
3
|
-
LslBindingsOutlet,
|
|
4
|
-
LslBindingsStreamInfo,
|
|
5
|
-
LslSample,
|
|
6
|
-
} from './Liblsl'
|
|
2
|
+
import LiblslImpl, { BoundOutlet, BoundStreamInfo, LslSample } from './Liblsl'
|
|
7
3
|
|
|
8
4
|
export default class LslOutletImpl implements LslOutlet {
|
|
9
5
|
private outletOptions: LslOutletOptions
|
|
10
6
|
public static Class?: new (options: LslOutletOptions) => LslOutlet
|
|
11
|
-
private streamInfo:
|
|
12
|
-
private outlet:
|
|
7
|
+
private streamInfo: BoundStreamInfo
|
|
8
|
+
private outlet: BoundOutlet
|
|
13
9
|
|
|
14
10
|
protected constructor(options: LslOutletOptions) {
|
|
15
11
|
const { sampleRate, channelFormat } = assertOptions(options, [
|
|
16
12
|
'name',
|
|
17
13
|
'type',
|
|
18
|
-
'
|
|
14
|
+
'channelNames',
|
|
19
15
|
'sampleRate',
|
|
20
16
|
'channelFormat',
|
|
21
17
|
'sourceId',
|
|
@@ -27,10 +23,12 @@ export default class LslOutletImpl implements LslOutlet {
|
|
|
27
23
|
|
|
28
24
|
this.outletOptions = options
|
|
29
25
|
|
|
30
|
-
const { chunkSize, maxBuffered,
|
|
26
|
+
const { chunkSize, maxBuffered, channelNames, ...streamInfoOptions } = this
|
|
31
27
|
.outletOptions as any
|
|
32
28
|
|
|
33
|
-
|
|
29
|
+
const channelCount = channelNames.length
|
|
30
|
+
|
|
31
|
+
this.assertValidChannelCount(channelCount)
|
|
34
32
|
this.assertValidSampleRate(sampleRate)
|
|
35
33
|
this.assertValidChannelFormat(channelFormat)
|
|
36
34
|
this.assertValidChunkSize(chunkSize)
|
|
@@ -41,18 +39,18 @@ export default class LslOutletImpl implements LslOutlet {
|
|
|
41
39
|
|
|
42
40
|
this.streamInfo = this.lsl.createStreamInfo({
|
|
43
41
|
...streamInfoOptions,
|
|
44
|
-
channelCount
|
|
45
|
-
channelFormat:
|
|
42
|
+
channelCount,
|
|
43
|
+
channelFormat: this.lookupChannelFormat(channelFormat),
|
|
46
44
|
})
|
|
47
45
|
|
|
48
|
-
this.lsl.appendChannelsToStreamInfo(
|
|
49
|
-
this.streamInfo,
|
|
50
|
-
|
|
46
|
+
this.lsl.appendChannelsToStreamInfo({
|
|
47
|
+
info: this.streamInfo,
|
|
48
|
+
channels: channelNames.map((label: string) => ({
|
|
51
49
|
label,
|
|
52
50
|
unit: this.outletOptions.unit,
|
|
53
51
|
type: this.outletOptions.type,
|
|
54
|
-
}))
|
|
55
|
-
)
|
|
52
|
+
})),
|
|
53
|
+
})
|
|
56
54
|
|
|
57
55
|
this.outlet = this.lsl.createOutlet({
|
|
58
56
|
info: this.streamInfo,
|
|
@@ -65,6 +63,14 @@ export default class LslOutletImpl implements LslOutlet {
|
|
|
65
63
|
return new (this.Class ?? this)(options)
|
|
66
64
|
}
|
|
67
65
|
|
|
66
|
+
public pushSample(sample: LslSample) {
|
|
67
|
+
this.lsl.pushSample({ outlet: this.outlet, sample })
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private lookupChannelFormat(channelFormat: ChannelFormat): number {
|
|
71
|
+
return CHANNEL_FORMATS.indexOf(channelFormat)
|
|
72
|
+
}
|
|
73
|
+
|
|
68
74
|
private assertValidMaxBufferred(maxBuffered: number) {
|
|
69
75
|
if (!this.isPositiveIntegerOrZero(maxBuffered)) {
|
|
70
76
|
throw new SchemaError({
|
|
@@ -87,7 +93,6 @@ export default class LslOutletImpl implements LslOutlet {
|
|
|
87
93
|
|
|
88
94
|
private assertValidChannelFormat(channelFormat: ChannelFormat) {
|
|
89
95
|
const validFormats = CHANNEL_FORMATS
|
|
90
|
-
|
|
91
96
|
if (validFormats.indexOf(channelFormat) === -1) {
|
|
92
97
|
throw new SchemaError({
|
|
93
98
|
code: 'INVALID_PARAMETERS',
|
|
@@ -98,11 +103,11 @@ export default class LslOutletImpl implements LslOutlet {
|
|
|
98
103
|
}
|
|
99
104
|
|
|
100
105
|
private assertValidSampleRate(sampleRate: number) {
|
|
101
|
-
if (!this.
|
|
106
|
+
if (!this.isGreaterThanZero(sampleRate)) {
|
|
102
107
|
throw new SchemaError({
|
|
103
108
|
code: 'INVALID_PARAMETERS',
|
|
104
109
|
parameters: ['sampleRate'],
|
|
105
|
-
friendlyMessage: 'Sample rate must be a positive number.',
|
|
110
|
+
friendlyMessage: 'Sample rate must be a positive number or zero.',
|
|
106
111
|
})
|
|
107
112
|
}
|
|
108
113
|
}
|
|
@@ -111,30 +116,26 @@ export default class LslOutletImpl implements LslOutlet {
|
|
|
111
116
|
if (!this.isPositiveInteger(channelCount)) {
|
|
112
117
|
throw new SchemaError({
|
|
113
118
|
code: 'INVALID_PARAMETERS',
|
|
114
|
-
parameters: ['
|
|
115
|
-
friendlyMessage: '
|
|
119
|
+
parameters: ['channelNames'],
|
|
120
|
+
friendlyMessage: 'channelNames must have 1 or more labels.',
|
|
116
121
|
})
|
|
117
122
|
}
|
|
118
123
|
}
|
|
119
124
|
|
|
120
|
-
private
|
|
121
|
-
return value
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
private isPositiveIntegerOrZero(value: number) {
|
|
125
|
-
return Number.isInteger(value) && value >= 0
|
|
125
|
+
private isGreaterThanZero(value: number) {
|
|
126
|
+
return value >= 0
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
private isPositiveInteger(value: number) {
|
|
129
130
|
return Number.isInteger(value) && value > 0
|
|
130
131
|
}
|
|
131
132
|
|
|
132
|
-
private
|
|
133
|
-
return
|
|
133
|
+
private isPositiveIntegerOrZero(value: number) {
|
|
134
|
+
return Number.isInteger(value) && value >= 0
|
|
134
135
|
}
|
|
135
136
|
|
|
136
|
-
|
|
137
|
-
|
|
137
|
+
private get lsl() {
|
|
138
|
+
return LiblslImpl.getInstance()
|
|
138
139
|
}
|
|
139
140
|
}
|
|
140
141
|
|
|
@@ -150,10 +151,15 @@ const CHANNEL_FORMATS = [
|
|
|
150
151
|
] as const
|
|
151
152
|
|
|
152
153
|
export type ChannelFormat = (typeof CHANNEL_FORMATS)[number]
|
|
154
|
+
|
|
155
|
+
export interface LslOutlet {
|
|
156
|
+
pushSample(sample: LslSample): void
|
|
157
|
+
}
|
|
158
|
+
|
|
153
159
|
export interface LslOutletOptions {
|
|
154
160
|
name: string
|
|
155
161
|
type: string
|
|
156
|
-
|
|
162
|
+
channelNames: string[]
|
|
157
163
|
sampleRate: number
|
|
158
164
|
channelFormat: ChannelFormat
|
|
159
165
|
sourceId: string
|
|
@@ -162,7 +168,3 @@ export interface LslOutletOptions {
|
|
|
162
168
|
chunkSize: number
|
|
163
169
|
maxBuffered: number
|
|
164
170
|
}
|
|
165
|
-
|
|
166
|
-
export interface LslOutlet {
|
|
167
|
-
pushSample(sample: LslSample): void
|
|
168
|
-
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import LslOutletImpl, { LslOutletOptions } from './LslOutlet'
|
|
2
|
+
|
|
3
|
+
export default class TimeMarkerOutlet extends LslOutletImpl {
|
|
4
|
+
public static Outlet(options?: Partial<LslOutletOptions>) {
|
|
5
|
+
const defaultOptions = {
|
|
6
|
+
name: 'Time markers',
|
|
7
|
+
type: 'Markers',
|
|
8
|
+
channelNames: ['Markers'],
|
|
9
|
+
sampleRate: 0,
|
|
10
|
+
channelFormat: 'string',
|
|
11
|
+
sourceId: 'time-markers',
|
|
12
|
+
manufacturer: 'N/A',
|
|
13
|
+
unit: 'N/A',
|
|
14
|
+
chunkSize: 0,
|
|
15
|
+
maxBuffered: 0,
|
|
16
|
+
} as LslOutletOptions
|
|
17
|
+
return new (this.Class ?? this)({ ...defaultOptions, ...options })
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -5,63 +5,37 @@ import AbstractSpruceTest, {
|
|
|
5
5
|
errorAssert,
|
|
6
6
|
generateId,
|
|
7
7
|
} from '@sprucelabs/test-utils'
|
|
8
|
-
import LiblslImpl, {
|
|
9
|
-
CreateOutletOptions,
|
|
10
|
-
CreateStreamInfoOptions,
|
|
11
|
-
Liblsl,
|
|
12
|
-
LslBindingsOutlet,
|
|
13
|
-
LslBindingsStreamInfo,
|
|
14
|
-
LslChannel,
|
|
15
|
-
LslSample,
|
|
16
|
-
} from '../../Liblsl'
|
|
8
|
+
import LiblslImpl, { LslSample } from '../../Liblsl'
|
|
17
9
|
import LslOutlet, { ChannelFormat, LslOutletOptions } from '../../LslOutlet'
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
'float32',
|
|
22
|
-
'double64',
|
|
23
|
-
'string',
|
|
24
|
-
'int32',
|
|
25
|
-
'int16',
|
|
26
|
-
'int8',
|
|
27
|
-
'int64',
|
|
28
|
-
] as const
|
|
10
|
+
import { TEST_CHANNEL_FORMATS } from '../support/consts'
|
|
11
|
+
import generateRandomOutletOptions from '../support/generateRandomOutletOptions'
|
|
12
|
+
import { SpyLiblsl } from '../support/SpyLiblsl'
|
|
29
13
|
|
|
30
14
|
export default class LslOutletTest extends AbstractSpruceTest {
|
|
31
|
-
private static
|
|
15
|
+
private static spyLiblsl: SpyLiblsl
|
|
32
16
|
private static randomOutletOptions: LslOutletOptions
|
|
33
|
-
private static
|
|
17
|
+
private static channelFormatIdx: number
|
|
34
18
|
|
|
35
19
|
protected static async beforeEach() {
|
|
36
20
|
await super.beforeEach()
|
|
37
21
|
delete LslOutlet.Class
|
|
38
|
-
this.
|
|
39
|
-
this.randomOutletOptions =
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
channelFormat: CHANNEL_FORMATS[this.channelIdx],
|
|
45
|
-
sourceId: generateId(),
|
|
46
|
-
manufacturer: generateId(),
|
|
47
|
-
unit: generateId(),
|
|
48
|
-
chunkSize: randomInt(0, 10),
|
|
49
|
-
maxBuffered: randomInt(0, 10),
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
this.spyLsl = new SpyLiblsl()
|
|
53
|
-
LiblslImpl.setInstance(this.spyLsl)
|
|
22
|
+
this.channelFormatIdx = randomInt(8)
|
|
23
|
+
this.randomOutletOptions = generateRandomOutletOptions(
|
|
24
|
+
this.channelFormatIdx
|
|
25
|
+
)
|
|
26
|
+
this.spyLiblsl = new SpyLiblsl()
|
|
27
|
+
LiblslImpl.setInstance(this.spyLiblsl)
|
|
54
28
|
}
|
|
55
29
|
|
|
56
30
|
@test()
|
|
57
|
-
protected static async
|
|
31
|
+
protected static async throwsWhenMissingRequiredParams() {
|
|
58
32
|
//@ts-ignore
|
|
59
33
|
const err = assert.doesThrow(() => new LslOutlet())
|
|
60
34
|
errorAssert.assertError(err, 'MISSING_PARAMETERS', {
|
|
61
35
|
parameters: [
|
|
62
36
|
'name',
|
|
63
37
|
'type',
|
|
64
|
-
'
|
|
38
|
+
'channelNames',
|
|
65
39
|
'sampleRate',
|
|
66
40
|
'channelFormat',
|
|
67
41
|
'sourceId',
|
|
@@ -74,18 +48,13 @@ export default class LslOutletTest extends AbstractSpruceTest {
|
|
|
74
48
|
}
|
|
75
49
|
|
|
76
50
|
@test()
|
|
77
|
-
protected static async
|
|
78
|
-
this.
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
@test()
|
|
82
|
-
protected static async throwsWithInvalidChannelCount() {
|
|
83
|
-
this.assertThrowsInvalidChannelLabels(0)
|
|
51
|
+
protected static async throwsWithInvalidChannelNames() {
|
|
52
|
+
this.assertThrowsInvalidChannelNames(0)
|
|
84
53
|
}
|
|
85
54
|
|
|
86
55
|
@test()
|
|
87
56
|
protected static async throwsWithInvalidSampleRate() {
|
|
88
|
-
this.
|
|
57
|
+
this.Outlet({ sampleRate: 0 })
|
|
89
58
|
this.assertThrowsInvalidSampleRate(-1)
|
|
90
59
|
this.assertThrowsInvalidSampleRate(-1.5)
|
|
91
60
|
}
|
|
@@ -99,15 +68,7 @@ export default class LslOutletTest extends AbstractSpruceTest {
|
|
|
99
68
|
}
|
|
100
69
|
|
|
101
70
|
@test()
|
|
102
|
-
protected static async
|
|
103
|
-
const valid = CHANNEL_FORMATS
|
|
104
|
-
for (const format of valid) {
|
|
105
|
-
this.Outlet({ channelFormat: format as ChannelFormat })
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
@test()
|
|
110
|
-
protected static async throwWithInvalidChunkSize() {
|
|
71
|
+
protected static async throwsWithInvalidChunkSize() {
|
|
111
72
|
this.Outlet({ chunkSize: 0 })
|
|
112
73
|
this.assertThrowsInvalidChunkSize(-1)
|
|
113
74
|
this.assertThrowsInvalidChunkSize(-1.5)
|
|
@@ -122,56 +83,66 @@ export default class LslOutletTest extends AbstractSpruceTest {
|
|
|
122
83
|
this.assertThrowsInvalidMaxBuffered(1.5)
|
|
123
84
|
}
|
|
124
85
|
|
|
125
|
-
@test(
|
|
126
|
-
|
|
127
|
-
|
|
86
|
+
@test()
|
|
87
|
+
protected static async supportsAllKnownChannelFormats() {
|
|
88
|
+
for (const format of TEST_CHANNEL_FORMATS) {
|
|
89
|
+
this.Outlet({ channelFormat: format as ChannelFormat })
|
|
90
|
+
}
|
|
91
|
+
}
|
|
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) {
|
|
128
96
|
this.pushSample(sample)
|
|
129
|
-
assert.isEqual(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
97
|
+
assert.isEqual(
|
|
98
|
+
this.spyLiblsl.lastPushSampleOptions?.outlet,
|
|
99
|
+
this.spyLiblsl.outlet
|
|
100
|
+
)
|
|
101
|
+
assert.isEqualDeep(this.spyLiblsl.lastPushSampleOptions?.sample, sample)
|
|
102
|
+
assert.isEqualDeep(this.spyLiblsl.lastCreateOutletOptions, {
|
|
103
|
+
info: this.spyLiblsl.streamInfo,
|
|
133
104
|
chunkSize: this.randomOutletOptions.chunkSize,
|
|
134
105
|
maxBuffered: this.randomOutletOptions.maxBuffered,
|
|
135
106
|
})
|
|
136
107
|
}
|
|
137
108
|
|
|
138
109
|
@test()
|
|
139
|
-
protected static async
|
|
110
|
+
protected static async constructionCreatesStreamInfo() {
|
|
140
111
|
this.Outlet()
|
|
141
112
|
|
|
142
113
|
const { ...options } = this.randomOutletOptions as any
|
|
143
114
|
|
|
144
|
-
options.channelCount = options.
|
|
115
|
+
options.channelCount = options.channelNames.length
|
|
145
116
|
|
|
146
117
|
delete options.manufacturer
|
|
147
118
|
delete options.unit
|
|
148
119
|
delete options.chunkSize
|
|
149
120
|
delete options.maxBuffered
|
|
150
|
-
delete options.
|
|
121
|
+
delete options.channelNames
|
|
151
122
|
|
|
152
|
-
assert.isEqualDeep(this.
|
|
123
|
+
assert.isEqualDeep(this.spyLiblsl.lastCreateStreamInfoOptions, {
|
|
153
124
|
...options,
|
|
154
|
-
channelFormat: this.
|
|
125
|
+
channelFormat: this.channelFormatIdx,
|
|
155
126
|
})
|
|
156
127
|
}
|
|
157
128
|
|
|
158
129
|
@test()
|
|
159
|
-
protected static async
|
|
130
|
+
protected static async canOverrideClassInstantiatedInFactory() {
|
|
160
131
|
LslOutlet.Class = CheckingOutlet
|
|
161
132
|
const instance = this.Outlet()
|
|
162
133
|
assert.isInstanceOf(instance, CheckingOutlet)
|
|
163
134
|
}
|
|
164
135
|
|
|
165
|
-
@test('can add one label', [generateId()])
|
|
166
|
-
@test('can add two labels', [generateId(), generateId()])
|
|
136
|
+
@test('can add one label to channels', [generateId()])
|
|
137
|
+
@test('can add two labels to channels', [generateId(), generateId()])
|
|
167
138
|
protected static async appendsChannelsBasedOnCount(labels: string[]) {
|
|
168
139
|
const type = generateId()
|
|
169
140
|
const unit = generateId()
|
|
170
141
|
|
|
171
|
-
this.Outlet({
|
|
142
|
+
this.Outlet({ channelNames: labels, type, unit })
|
|
172
143
|
|
|
173
|
-
assert.isEqualDeep(this.
|
|
174
|
-
info: this.
|
|
144
|
+
assert.isEqualDeep(this.spyLiblsl.lastAppendChannelsToStreamInfoOptions, {
|
|
145
|
+
info: this.spyLiblsl.streamInfo,
|
|
175
146
|
channels: labels.map((label) => ({
|
|
176
147
|
label,
|
|
177
148
|
type,
|
|
@@ -187,7 +158,7 @@ export default class LslOutletTest extends AbstractSpruceTest {
|
|
|
187
158
|
outlet.pushSample([2])
|
|
188
159
|
outlet.pushSample([3])
|
|
189
160
|
|
|
190
|
-
assert.isEqual(this.
|
|
161
|
+
assert.isEqual(this.spyLiblsl.createStreamInfoHitCount, 1)
|
|
191
162
|
}
|
|
192
163
|
|
|
193
164
|
private static pushSample(sample: LslSample) {
|
|
@@ -207,10 +178,10 @@ export default class LslOutletTest extends AbstractSpruceTest {
|
|
|
207
178
|
this.assertDoesThrowInvalidParameters({ sampleRate }, ['sampleRate'])
|
|
208
179
|
}
|
|
209
180
|
|
|
210
|
-
private static
|
|
181
|
+
private static assertThrowsInvalidChannelNames(count: number) {
|
|
211
182
|
this.assertDoesThrowInvalidParameters(
|
|
212
|
-
{
|
|
213
|
-
['
|
|
183
|
+
{ channelNames: new Array(count).fill(generateId()) },
|
|
184
|
+
['channelNames']
|
|
214
185
|
)
|
|
215
186
|
}
|
|
216
187
|
|
|
@@ -232,46 +203,6 @@ export default class LslOutletTest extends AbstractSpruceTest {
|
|
|
232
203
|
}
|
|
233
204
|
}
|
|
234
205
|
|
|
235
|
-
class SpyLiblsl implements Liblsl {
|
|
236
|
-
public lastPushedOutlet?: LslBindingsOutlet
|
|
237
|
-
public lastPushedSample?: LslSample
|
|
238
|
-
public lastOutletOptions?: CreateOutletOptions
|
|
239
|
-
public lastStreamInfoOptions?: CreateStreamInfoOptions
|
|
240
|
-
public outlet: LslBindingsOutlet = {} as LslBindingsOutlet
|
|
241
|
-
public streamInfo: LslBindingsStreamInfo = {} as LslBindingsStreamInfo
|
|
242
|
-
public lastAppendChannelsToStreamInfo?: {
|
|
243
|
-
info: LslBindingsStreamInfo
|
|
244
|
-
channels: LslChannel[]
|
|
245
|
-
}
|
|
246
|
-
public createStreamInfoHitCount = 0
|
|
247
|
-
|
|
248
|
-
public createStreamInfo(
|
|
249
|
-
options: CreateStreamInfoOptions
|
|
250
|
-
): LslBindingsStreamInfo {
|
|
251
|
-
this.createStreamInfoHitCount++
|
|
252
|
-
this.lastStreamInfoOptions = options
|
|
253
|
-
return this.streamInfo
|
|
254
|
-
}
|
|
255
|
-
public appendChannelsToStreamInfo(
|
|
256
|
-
info: LslBindingsStreamInfo,
|
|
257
|
-
channels: LslChannel[]
|
|
258
|
-
): void {
|
|
259
|
-
this.lastAppendChannelsToStreamInfo = {
|
|
260
|
-
info,
|
|
261
|
-
channels,
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
public createOutlet(options: CreateOutletOptions): LslBindingsOutlet {
|
|
266
|
-
this.lastOutletOptions = options
|
|
267
|
-
return this.outlet
|
|
268
|
-
}
|
|
269
|
-
public pushSample(outlet: LslBindingsOutlet, sample: LslSample): void {
|
|
270
|
-
this.lastPushedOutlet = outlet
|
|
271
|
-
this.lastPushedSample = sample
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
206
|
class CheckingOutlet extends LslOutlet {
|
|
276
207
|
public constructor(options: LslOutletOptions) {
|
|
277
208
|
super(options)
|