edfcore 0.2.18 → 0.2.19
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/CHANGELOG.md +11 -0
- package/dist/chunks.d.ts.map +1 -1
- package/dist/chunks.js +29 -3
- package/dist/chunks.js.map +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/package.json +1 -1
- package/src/chunks.ts +33 -3
- package/src/constants.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,17 @@ 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.2.19
|
|
10
|
+
|
|
11
|
+
- **Fixed** `mergeChunks` merging across a real gap whenever the index had not been scanned — the
|
|
12
|
+
one thing the helper exists to refuse. Its gap check read `precededByGap`, which is `undefined`
|
|
13
|
+
in two different situations: no gap, and nobody looked. `openEdf` returns a probed index that has
|
|
14
|
+
looked for none, and `readRecords` reads by record number without consulting the timeline, so two
|
|
15
|
+
chunks five seconds apart arrived record-adjacent with that field empty on both and were joined
|
|
16
|
+
silently. The refusal now compares the chunks' own `startSeconds` in exact ticks — each chunk
|
|
17
|
+
decoded its onset from its own bytes, so the evidence was in hand the whole time and never needed
|
|
18
|
+
an index. The `precededByGap` branch stays, because its message names the indexed gap's duration.
|
|
19
|
+
|
|
9
20
|
## 0.2.18
|
|
10
21
|
|
|
11
22
|
- **Fixed** `readTriggers` timing every event on the nominal grid — `sampleIndex * recordDuration /
|
package/dist/chunks.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chunks.d.ts","sourceRoot":"","sources":["../src/chunks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;
|
|
1
|
+
{"version":3,"file":"chunks.d.ts","sourceRoot":"","sources":["../src/chunks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAiC,MAAM,YAAY,CAAC;AA4F1E;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,QAAQ,EAAE,GAAG,QAAQ,CAwEjE"}
|
package/dist/chunks.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* convention every option check in the package follows.
|
|
16
16
|
*/
|
|
17
17
|
import { appendDiagnostics } from './diagnostics/collector.js';
|
|
18
|
+
import { secondsToTicks, ticksToSeconds } from './tal/ticks.js';
|
|
18
19
|
/** Reads as one line at the call site, and keeps the `chunks[i]` non-null assertions out of it. */
|
|
19
20
|
function at(chunks, index) {
|
|
20
21
|
const chunk = chunks[index];
|
|
@@ -25,9 +26,19 @@ function at(chunks, index) {
|
|
|
25
26
|
/**
|
|
26
27
|
* Everything that makes two chunks joinable, checked before a byte is allocated.
|
|
27
28
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
29
|
+
* Three tests, and the second and third are the ones that earn their place.
|
|
30
|
+
*
|
|
31
|
+
* `precededByGap` alone is not enough, because it is `undefined` in two different situations: no
|
|
32
|
+
* gap, and nobody looked. `openEdf` returns a probed index, `gapBefore` has nothing to report from
|
|
33
|
+
* one, and `readRecords` reads by record number without ever consulting the timeline — so two
|
|
34
|
+
* chunks a minute apart on an EDF+D file arrive record-adjacent with `precededByGap: undefined` on
|
|
35
|
+
* both, and the field the refusal was keyed on says nothing at all. Every chunk carries its own
|
|
36
|
+
* `startSeconds`, decoded from the annotation regions in its own bytes, so the evidence was in hand
|
|
37
|
+
* the whole time: the second test compares the clock instead of asking the index.
|
|
38
|
+
*
|
|
39
|
+
* The per-signal sample-index test is the third: `trimToWindow` narrows a chunk on each signal's
|
|
40
|
+
* own grid without changing the chunk's `durationSeconds`, so a trimmed chunk passes both the
|
|
41
|
+
* record and the clock test while the samples between the two are gone. Comparing
|
|
31
42
|
* `firstSampleIndex` against the previous chunk's end catches exactly that, per signal, which is
|
|
32
43
|
* the granularity at which it actually happens.
|
|
33
44
|
*/
|
|
@@ -43,6 +54,21 @@ function assertJoinable(previous, next, index) {
|
|
|
43
54
|
throw new RangeError(`mergeChunks: chunk ${index} starts at record ${next.records.start}, but the chunk before ` +
|
|
44
55
|
`it ends at ${expectedStart}. Chunks must be adjacent and in order.`);
|
|
45
56
|
}
|
|
57
|
+
// In exact ticks, never in float seconds: both values were produced from ticks by
|
|
58
|
+
// `ticksToSeconds`, and rounding back recovers the tick they came from for any recording
|
|
59
|
+
// shorter than ~28.5 years — the same round trip `trimToWindow` relies on. A float comparison
|
|
60
|
+
// here would let a sub-tick discrepancy through, and an epsilon would let a real one through.
|
|
61
|
+
const previousEndTicks = secondsToTicks(previous.startSeconds) + secondsToTicks(previous.durationSeconds);
|
|
62
|
+
const nextStartTicks = secondsToTicks(next.startSeconds);
|
|
63
|
+
if (previousEndTicks !== nextStartTicks) {
|
|
64
|
+
const gapSeconds = ticksToSeconds(nextStartTicks - previousEndTicks);
|
|
65
|
+
throw new RangeError(`mergeChunks: chunk ${index} starts at ${next.startSeconds} s, but the chunk before it ends ` +
|
|
66
|
+
`at ${ticksToSeconds(previousEndTicks)} s — a discontinuity of ${gapSeconds} s. The two ` +
|
|
67
|
+
'are record-adjacent, so this is a gap in TIME that the record numbers cannot show: ' +
|
|
68
|
+
'either the index was never scanned, or these chunks came from separate reads. ' +
|
|
69
|
+
'Concatenating them would date every sample after the join wrong by that much. Next: ' +
|
|
70
|
+
'await buildRecordIndex(recording) and merge each contiguous run separately.');
|
|
71
|
+
}
|
|
46
72
|
if (next.signals.length !== previous.signals.length) {
|
|
47
73
|
throw new RangeError(`mergeChunks: chunk ${index} carries ${next.signals.length} signal(s), the chunk before it ` +
|
|
48
74
|
`${previous.signals.length}. Every chunk must have been read with the same signal selection.`);
|
package/dist/chunks.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chunks.js","sourceRoot":"","sources":["../src/chunks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"chunks.js","sourceRoot":"","sources":["../src/chunks.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.2.
|
|
112
|
+
export declare const VERSION = "0.2.19";
|
|
113
113
|
//# sourceMappingURL=constants.d.ts.map
|
package/dist/constants.js
CHANGED
package/package.json
CHANGED
package/src/chunks.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { appendDiagnostics } from './diagnostics/collector.js';
|
|
19
|
+
import { secondsToTicks, ticksToSeconds } from './tal/ticks.js';
|
|
19
20
|
import type { EdfChunk, EdfChunkSignal, EdfDiagnostic } from './types.js';
|
|
20
21
|
|
|
21
22
|
/** Reads as one line at the call site, and keeps the `chunks[i]` non-null assertions out of it. */
|
|
@@ -28,9 +29,19 @@ function at(chunks: readonly EdfChunk[], index: number): EdfChunk {
|
|
|
28
29
|
/**
|
|
29
30
|
* Everything that makes two chunks joinable, checked before a byte is allocated.
|
|
30
31
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
32
|
+
* Three tests, and the second and third are the ones that earn their place.
|
|
33
|
+
*
|
|
34
|
+
* `precededByGap` alone is not enough, because it is `undefined` in two different situations: no
|
|
35
|
+
* gap, and nobody looked. `openEdf` returns a probed index, `gapBefore` has nothing to report from
|
|
36
|
+
* one, and `readRecords` reads by record number without ever consulting the timeline — so two
|
|
37
|
+
* chunks a minute apart on an EDF+D file arrive record-adjacent with `precededByGap: undefined` on
|
|
38
|
+
* both, and the field the refusal was keyed on says nothing at all. Every chunk carries its own
|
|
39
|
+
* `startSeconds`, decoded from the annotation regions in its own bytes, so the evidence was in hand
|
|
40
|
+
* the whole time: the second test compares the clock instead of asking the index.
|
|
41
|
+
*
|
|
42
|
+
* The per-signal sample-index test is the third: `trimToWindow` narrows a chunk on each signal's
|
|
43
|
+
* own grid without changing the chunk's `durationSeconds`, so a trimmed chunk passes both the
|
|
44
|
+
* record and the clock test while the samples between the two are gone. Comparing
|
|
34
45
|
* `firstSampleIndex` against the previous chunk's end catches exactly that, per signal, which is
|
|
35
46
|
* the granularity at which it actually happens.
|
|
36
47
|
*/
|
|
@@ -52,6 +63,25 @@ function assertJoinable(previous: EdfChunk, next: EdfChunk, index: number): void
|
|
|
52
63
|
);
|
|
53
64
|
}
|
|
54
65
|
|
|
66
|
+
// In exact ticks, never in float seconds: both values were produced from ticks by
|
|
67
|
+
// `ticksToSeconds`, and rounding back recovers the tick they came from for any recording
|
|
68
|
+
// shorter than ~28.5 years — the same round trip `trimToWindow` relies on. A float comparison
|
|
69
|
+
// here would let a sub-tick discrepancy through, and an epsilon would let a real one through.
|
|
70
|
+
const previousEndTicks =
|
|
71
|
+
secondsToTicks(previous.startSeconds) + secondsToTicks(previous.durationSeconds);
|
|
72
|
+
const nextStartTicks = secondsToTicks(next.startSeconds);
|
|
73
|
+
if (previousEndTicks !== nextStartTicks) {
|
|
74
|
+
const gapSeconds = ticksToSeconds(nextStartTicks - previousEndTicks);
|
|
75
|
+
throw new RangeError(
|
|
76
|
+
`mergeChunks: chunk ${index} starts at ${next.startSeconds} s, but the chunk before it ends ` +
|
|
77
|
+
`at ${ticksToSeconds(previousEndTicks)} s — a discontinuity of ${gapSeconds} s. The two ` +
|
|
78
|
+
'are record-adjacent, so this is a gap in TIME that the record numbers cannot show: ' +
|
|
79
|
+
'either the index was never scanned, or these chunks came from separate reads. ' +
|
|
80
|
+
'Concatenating them would date every sample after the join wrong by that much. Next: ' +
|
|
81
|
+
'await buildRecordIndex(recording) and merge each contiguous run separately.',
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
55
85
|
if (next.signals.length !== previous.signals.length) {
|
|
56
86
|
throw new RangeError(
|
|
57
87
|
`mergeChunks: chunk ${index} carries ${next.signals.length} signal(s), the chunk before it ` +
|
package/src/constants.ts
CHANGED