edfcore 0.6.79 → 0.6.80
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/dist/biosemi.d.ts.map +1 -1
- package/dist/biosemi.js +34 -0
- package/dist/biosemi.js.map +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/docs/CHANGELOG.md +19 -0
- package/package.json +1 -1
- package/src/biosemi.ts +34 -0
- package/src/constants.ts +1 -1
package/dist/biosemi.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"biosemi.d.ts","sourceRoot":"","sources":["../src/biosemi.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;
|
|
1
|
+
{"version":3,"file":"biosemi.d.ts","sourceRoot":"","sources":["../src/biosemi.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AASH,OAAO,KAAK,EACV,SAAS,EACT,YAAY,EAEZ,SAAS,EACT,aAAa,EACb,eAAe,EACf,WAAW,EAEX,gBAAgB,EACjB,MAAM,YAAY,CAAC;AA2CpB;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAOxE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAgC9D;AAqBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,wBAAsB,YAAY,CAChC,SAAS,EAAE,YAAY,EACvB,SAAS,EAAE,gBAAgB,EAC3B,OAAO,CAAC,EAAE,WAAW,GACpB,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC,CAoIrC"}
|
package/dist/biosemi.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* meanings for the undocumented ones would be guessing, and a wrong trigger code is worse than
|
|
16
16
|
* none.
|
|
17
17
|
*/
|
|
18
|
+
import { BDF_DIGITAL_MIN } from './constants.js';
|
|
18
19
|
import { decodeDigitalCounted } from './decode/digital.js';
|
|
19
20
|
import { readRecordBytes } from './io/read.js';
|
|
20
21
|
import { scanChunkRecords } from './record-index.js';
|
|
@@ -23,6 +24,16 @@ import { ceilDiv, secondsToTicks, ticksToSeconds } from './tal/ticks.js';
|
|
|
23
24
|
import { resolveTimeWindow } from './time/window.js';
|
|
24
25
|
/** BioSemi's own label for the channel. Matched case-insensitively after trimming. */
|
|
25
26
|
const STATUS_LABEL = 'status';
|
|
27
|
+
/** `undefined`, `a string`, `NaN`, `1.5` — named as itself, since `&` would have taken them all. */
|
|
28
|
+
function describeSample(sample) {
|
|
29
|
+
if (sample === null)
|
|
30
|
+
return 'null';
|
|
31
|
+
if (sample === undefined)
|
|
32
|
+
return 'undefined';
|
|
33
|
+
if (typeof sample !== 'number')
|
|
34
|
+
return `a ${typeof sample}`;
|
|
35
|
+
return String(sample);
|
|
36
|
+
}
|
|
26
37
|
/*
|
|
27
38
|
* The Status word, as BioSemi assigns it ("Trigger signals", biosemi.com; the same table is in
|
|
28
39
|
* BIOSIG/FieldTrip's `read_biosemi_bdf`):
|
|
@@ -50,6 +61,8 @@ const TRIGGER_MASK = 0xffff;
|
|
|
50
61
|
const EPOCH_BIT = 1 << 16;
|
|
51
62
|
const CMS_IN_RANGE_BIT = 1 << 20;
|
|
52
63
|
const BATTERY_LOW_BIT = 1 << 22;
|
|
64
|
+
/** The widest a 24-bit word goes when written unsigned; `BDF_DIGITAL_MIN` is the other end. */
|
|
65
|
+
const UNSIGNED_24_BIT_MAX = 0xffffff;
|
|
53
66
|
/**
|
|
54
67
|
* The `Status` channel of a BDF file, or `undefined` when there is none.
|
|
55
68
|
*
|
|
@@ -74,6 +87,27 @@ export function getStatusSignal(header) {
|
|
|
74
87
|
* anything is read out of it.
|
|
75
88
|
*/
|
|
76
89
|
export function decodeStatusWord(sample) {
|
|
90
|
+
/*
|
|
91
|
+
* Guarded, because `&` coerces rather than refuses. `undefined`, `null`, `NaN` and a string all
|
|
92
|
+
* become 0 under it, and 0 is a perfectly well-formed Status word — no trigger asserted, CMS in
|
|
93
|
+
* range, battery fine. A caller indexing the wrong array got that back with no way to tell it
|
|
94
|
+
* from a real sample, which is the outcome this library exists to prevent: a wrong value that
|
|
95
|
+
* looks like a value. `1.5` truncated to trigger code 1, and anything wider than 24 bits was
|
|
96
|
+
* masked away rather than questioned (fixed in 0.6.80).
|
|
97
|
+
*
|
|
98
|
+
* The bound admits BOTH spellings of a 24-bit word, because both arrive here legitimately.
|
|
99
|
+
* `decodeDigital` sign-extends, so a real sample with bit 23 set is negative and the mask below
|
|
100
|
+
* is what puts it back; a caller writing a bit pattern by hand spells the same word unsigned, up
|
|
101
|
+
* to 0xffffff. What is refused is everything outside the union: a wider integer, a fraction, and
|
|
102
|
+
* the values `&` silently turned into zero.
|
|
103
|
+
*/
|
|
104
|
+
if (!Number.isSafeInteger(sample) || sample < BDF_DIGITAL_MIN || sample > UNSIGNED_24_BIT_MAX) {
|
|
105
|
+
throw new RangeError(`decodeStatusWord(): ${describeSample(sample)} is not a 24-bit Status word, which is a ` +
|
|
106
|
+
`whole number in ${BDF_DIGITAL_MIN}..${UNSIGNED_24_BIT_MAX} — sign-extended as ` +
|
|
107
|
+
'decodeDigital() returns it, or unsigned as a bit pattern is written. Next: pass one ' +
|
|
108
|
+
'element of the Int32Array decodeDigital() returned for the Status channel, or call ' +
|
|
109
|
+
'readTriggers() and let edfcore find that channel and decode it.');
|
|
110
|
+
}
|
|
77
111
|
const raw = sample & 0xffffff;
|
|
78
112
|
return {
|
|
79
113
|
raw,
|
package/dist/biosemi.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"biosemi.js","sourceRoot":"","sources":["../src/biosemi.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAarD,sFAAsF;AACtF,MAAM,YAAY,GAAG,QAAQ,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,YAAY,GAAG,MAAM,CAAC;AAC5B,MAAM,SAAS,GAAG,CAAC,IAAI,EAAE,CAAC;AAC1B,MAAM,gBAAgB,GAAG,CAAC,IAAI,EAAE,CAAC;AACjC,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"biosemi.js","sourceRoot":"","sources":["../src/biosemi.ts"],"names":[],"mappings":"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"}
|
package/dist/constants.d.ts
CHANGED
|
@@ -109,5 +109,5 @@ export declare const SIGNAL_FIELD_BLOCK_OFFSETS: {
|
|
|
109
109
|
readonly reserved: 224;
|
|
110
110
|
};
|
|
111
111
|
/** Published package version. Kept in sync with package.json by a test. */
|
|
112
|
-
export declare const VERSION = "0.6.
|
|
112
|
+
export declare const VERSION = "0.6.80";
|
|
113
113
|
//# sourceMappingURL=constants.d.ts.map
|
package/dist/constants.js
CHANGED
package/docs/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,25 @@ alone does not tell you whether you were affected.
|
|
|
6
6
|
edfcore is pre-1.0. Patch releases have carried behaviour changes where the old behaviour was a
|
|
7
7
|
defect; those are called out below.
|
|
8
8
|
|
|
9
|
+
## 0.6.80
|
|
10
|
+
|
|
11
|
+
- **Changed** `decodeStatusWord` to refuse a value that is not a BDF sample. It was
|
|
12
|
+
`sample & 0xffffff` and four bit tests, and in JavaScript `&` coerces rather than refuses:
|
|
13
|
+
`undefined`, `null`, `NaN` and a string all became `0` — a perfectly well-formed Status word
|
|
14
|
+
with no trigger asserted, CMS in range and the battery fine.
|
|
15
|
+
- That is the clause the library exists for, and the value was not even visibly wrong: a Status
|
|
16
|
+
word of zero is the most ordinary sample in a BDF file, so a caller indexing the wrong array had
|
|
17
|
+
nothing to tell it apart from a real one. `1.5` truncated to trigger code 1, and anything wider
|
|
18
|
+
than 24 bits was masked down rather than questioned.
|
|
19
|
+
- The bound admits both spellings of a 24-bit word, `-8388608..16777215`, because both arrive
|
|
20
|
+
legitimately: `decodeDigital` sign-extends, so a real sample with bit 23 set is negative and the
|
|
21
|
+
mask is what puts it back, while a caller writing a bit pattern by hand spells the same word
|
|
22
|
+
unsigned. A first draft of this guard took only the signed half and the existing
|
|
23
|
+
`biosemi.test.ts` rejected it on an idle MK2 word — which is what that test is for.
|
|
24
|
+
- A caller mistake, so a plain `RangeError` with a `Next:` clause naming what to pass. This is the
|
|
25
|
+
same hole 0.6.79 closed on the reading entry points, in a function small enough that it had no
|
|
26
|
+
guard at all.
|
|
27
|
+
|
|
9
28
|
## 0.6.79
|
|
10
29
|
|
|
11
30
|
- **Fixed** five reading entry points crashing with a bare `TypeError` when the selection argument
|
package/package.json
CHANGED
package/src/biosemi.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* none.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
+
import { BDF_DIGITAL_MIN } from './constants.js';
|
|
19
20
|
import { decodeDigitalCounted } from './decode/digital.js';
|
|
20
21
|
import { readRecordBytes } from './io/read.js';
|
|
21
22
|
import { scanChunkRecords } from './record-index.js';
|
|
@@ -37,6 +38,14 @@ import type {
|
|
|
37
38
|
/** BioSemi's own label for the channel. Matched case-insensitively after trimming. */
|
|
38
39
|
const STATUS_LABEL = 'status';
|
|
39
40
|
|
|
41
|
+
/** `undefined`, `a string`, `NaN`, `1.5` — named as itself, since `&` would have taken them all. */
|
|
42
|
+
function describeSample(sample: unknown): string {
|
|
43
|
+
if (sample === null) return 'null';
|
|
44
|
+
if (sample === undefined) return 'undefined';
|
|
45
|
+
if (typeof sample !== 'number') return `a ${typeof sample}`;
|
|
46
|
+
return String(sample);
|
|
47
|
+
}
|
|
48
|
+
|
|
40
49
|
/*
|
|
41
50
|
* The Status word, as BioSemi assigns it ("Trigger signals", biosemi.com; the same table is in
|
|
42
51
|
* BIOSIG/FieldTrip's `read_biosemi_bdf`):
|
|
@@ -64,6 +73,8 @@ const TRIGGER_MASK = 0xffff;
|
|
|
64
73
|
const EPOCH_BIT = 1 << 16;
|
|
65
74
|
const CMS_IN_RANGE_BIT = 1 << 20;
|
|
66
75
|
const BATTERY_LOW_BIT = 1 << 22;
|
|
76
|
+
/** The widest a 24-bit word goes when written unsigned; `BDF_DIGITAL_MIN` is the other end. */
|
|
77
|
+
const UNSIGNED_24_BIT_MAX = 0xffffff;
|
|
67
78
|
|
|
68
79
|
/**
|
|
69
80
|
* The `Status` channel of a BDF file, or `undefined` when there is none.
|
|
@@ -88,6 +99,29 @@ export function getStatusSignal(header: EdfHeader): EdfSignal | undefined {
|
|
|
88
99
|
* anything is read out of it.
|
|
89
100
|
*/
|
|
90
101
|
export function decodeStatusWord(sample: number): EdfStatusWord {
|
|
102
|
+
/*
|
|
103
|
+
* Guarded, because `&` coerces rather than refuses. `undefined`, `null`, `NaN` and a string all
|
|
104
|
+
* become 0 under it, and 0 is a perfectly well-formed Status word — no trigger asserted, CMS in
|
|
105
|
+
* range, battery fine. A caller indexing the wrong array got that back with no way to tell it
|
|
106
|
+
* from a real sample, which is the outcome this library exists to prevent: a wrong value that
|
|
107
|
+
* looks like a value. `1.5` truncated to trigger code 1, and anything wider than 24 bits was
|
|
108
|
+
* masked away rather than questioned (fixed in 0.6.80).
|
|
109
|
+
*
|
|
110
|
+
* The bound admits BOTH spellings of a 24-bit word, because both arrive here legitimately.
|
|
111
|
+
* `decodeDigital` sign-extends, so a real sample with bit 23 set is negative and the mask below
|
|
112
|
+
* is what puts it back; a caller writing a bit pattern by hand spells the same word unsigned, up
|
|
113
|
+
* to 0xffffff. What is refused is everything outside the union: a wider integer, a fraction, and
|
|
114
|
+
* the values `&` silently turned into zero.
|
|
115
|
+
*/
|
|
116
|
+
if (!Number.isSafeInteger(sample) || sample < BDF_DIGITAL_MIN || sample > UNSIGNED_24_BIT_MAX) {
|
|
117
|
+
throw new RangeError(
|
|
118
|
+
`decodeStatusWord(): ${describeSample(sample)} is not a 24-bit Status word, which is a ` +
|
|
119
|
+
`whole number in ${BDF_DIGITAL_MIN}..${UNSIGNED_24_BIT_MAX} — sign-extended as ` +
|
|
120
|
+
'decodeDigital() returns it, or unsigned as a bit pattern is written. Next: pass one ' +
|
|
121
|
+
'element of the Int32Array decodeDigital() returned for the Status channel, or call ' +
|
|
122
|
+
'readTriggers() and let edfcore find that channel and decode it.',
|
|
123
|
+
);
|
|
124
|
+
}
|
|
91
125
|
const raw = sample & 0xffffff;
|
|
92
126
|
return {
|
|
93
127
|
raw,
|
package/src/constants.ts
CHANGED