edfcore 0.6.309 → 0.6.310

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.
@@ -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.309";
112
+ export declare const VERSION = "0.6.310";
113
113
  //# sourceMappingURL=constants.d.ts.map
package/dist/constants.js CHANGED
@@ -79,5 +79,5 @@ export const SIGNAL_FIELD_BLOCK_OFFSETS = {
79
79
  reserved: 224,
80
80
  };
81
81
  /** Published package version. Kept in sync with package.json by a test. */
82
- export const VERSION = '0.6.309';
82
+ export const VERSION = '0.6.310';
83
83
  //# sourceMappingURL=constants.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sample-grid.d.ts","sourceRoot":"","sources":["../src/sample-grid.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAKH,OAAO,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AA+E/D;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,MAAM,EACf,mBAAmB,EAAE,MAAM,GAC1B,iBAAiB,CAoBnB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,MAAM,EACnB,mBAAmB,EAAE,MAAM,GAC1B,MAAM,CAUR;AAED,+EAA+E;AAC/E,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,MAAM,EACnB,mBAAmB,EAAE,MAAM,GAC1B,MAAM,CASR"}
1
+ {"version":3,"file":"sample-grid.d.ts","sourceRoot":"","sources":["../src/sample-grid.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAKH,OAAO,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AA+H/D;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,MAAM,EACf,mBAAmB,EAAE,MAAM,GAC1B,iBAAiB,CAoBnB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,MAAM,EACnB,mBAAmB,EAAE,MAAM,GAC1B,MAAM,CAUR;AAED,+EAA+E;AAC/E,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,MAAM,EACnB,mBAAmB,EAAE,MAAM,GAC1B,MAAM,CASR"}
@@ -41,8 +41,14 @@
41
41
  * entire point.
42
42
  */
43
43
  import { TICKS_PER_SECOND } from './constants.js';
44
- import { secondsToTicks } from './tal/ticks.js';
44
+ import { secondsToTicks, ticksToSeconds } from './tal/ticks.js';
45
45
  import { describeValue } from './text/describe.js';
46
+ /**
47
+ * How far the implied sample rate may sit from the declared one before the two arguments are taken
48
+ * to be from different headers. See `assertGrid`: the noise floor for a matched pair is 3.2e-16
49
+ * relative and the floor for a genuine mismatch is 1.1e-7, both measured.
50
+ */
51
+ const GRID_RATE_TOLERANCE = 1e-11;
46
52
  function assertGrid(signal, recordDurationTicks, call) {
47
53
  /*
48
54
  * Both arguments, before either is read. `signal.kind` on a chunk signal — the per-signal shape a
@@ -86,6 +92,45 @@ function assertGrid(signal, recordDurationTicks, call) {
86
92
  'elapsed time maps to a sample. This is legal EDF and a scoring file relies on it. ' +
87
93
  'Next: index by record with readRecords().');
88
94
  }
95
+ /*
96
+ * And that the two arguments came from the SAME HEADER, which nothing above can see.
97
+ *
98
+ * This family takes the signal and the record duration separately, and only the caller pairs
99
+ * them. Mix them and the grid is silently wrong by the ratio of the two durations: a 10 Hz signal
100
+ * asked with another file's 2 s ticks answered `sampleIndex: 5` at t = 1 s where the truth is 10.
101
+ * No throw, no diagnostic, half the index. `decodeDigital` refuses the analogous bytes-against-
102
+ * records mismatch and says why — it "cannot tell which record a differently sized buffer begins
103
+ * at, so it will not guess".
104
+ *
105
+ * `sampleRateHz` is the only thing bridging them, and it is a float: the header parses
106
+ * `recordDurationSeconds` from the field independently of `recordDurationTicks`, so this compares
107
+ * two reconstructions of one decimal rather than two copies of a number.
108
+ *
109
+ * WHICH IS WHY THIS IS A TOLERANCE AND NOT AN EQUALITY. `ticksToSeconds` rebuilds the double as
110
+ * `whole + remainder / 1e7` — two roundings — so for a legitimate file it need not equal the
111
+ * parsed float. Measured over 7,729 header spellings an 8-byte field can hold, the worst
112
+ * disagreement for a MATCHED pair is 3.2e-16 relative, and an exact check refused about one file
113
+ * in a hundred. The closest two genuinely DIFFERENT headers come is 1.1e-7, which is forced: eight
114
+ * ASCII bytes carry at most seven significant digits, so two distinct durations differ by at least
115
+ * about one part in 10^7.
116
+ *
117
+ * 1e-11 sits roughly 31,000x above the noise and 11,000x below the signal. The repository's
118
+ * distrust of epsilons — "a float tolerance is how a one-sample overlap becomes invisible" — is
119
+ * about TICK comparisons, where exactness is available. Here it is not, and the two populations
120
+ * are eight orders of magnitude apart (0.6.310).
121
+ */
122
+ const declaredRate = signal.sampleRateHz;
123
+ if (declaredRate !== undefined && declaredRate > 0) {
124
+ const impliedRate = signal.samplesPerRecord / ticksToSeconds(recordDurationTicks);
125
+ if (Math.abs(impliedRate - declaredRate) > declaredRate * GRID_RATE_TOLERANCE) {
126
+ throw new RangeError(`${call}(): signal ${signal.index} (${JSON.stringify(signal.label)}) samples at ` +
127
+ `${declaredRate} Hz, but ${ticksToSeconds(recordDurationTicks)} s per record puts its ` +
128
+ `${signal.samplesPerRecord} samples at ${impliedRate} Hz — so the signal and the record ` +
129
+ 'duration came from different headers, and the grid this would measure belongs to ' +
130
+ 'neither file. Next: pass header.recordDurationTicks from the same header the signal ' +
131
+ 'came out of.');
132
+ }
133
+ }
89
134
  }
90
135
  /**
91
136
  * The INDEX, named by the call the reader actually wrote.
@@ -1 +1 @@
1
- {"version":3,"file":"sample-grid.js","sourceRoot":"","sources":["../src/sample-grid.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sample-grid.js","sourceRoot":"","sources":["../src/sample-grid.ts"],"names":[],"mappings":"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"}
package/docs/CHANGELOG.md CHANGED
@@ -6,6 +6,31 @@ 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.310
10
+
11
+ - **Fixed** the grid family answering with another file's grid. `gridSampleIndexAt`,
12
+ `gridSampleStartTicks` and `gridSampleStartSeconds` take the signal and the record duration as
13
+ separate arguments, and only the caller pairs them — so a 10 Hz signal asked with another file's
14
+ 2 s ticks answered `sampleIndex: 5` at t = 1 s where its own grid says 10. No throw, no
15
+ diagnostic, half the index. `decodeDigital` refuses the analogous bytes-against-records mismatch
16
+ and says why: it "cannot tell which record a differently sized buffer begins at, so it will not
17
+ guess".
18
+ - **This was found in 0.6.299's batch and deliberately not shipped then**, on the reasoning that the
19
+ only bridge between the two arguments is `sampleRateHz` — a float — but that for a field of at most
20
+ seven decimals both sides round the same exact rational, so an exact check would be safe.
21
+ - **That reasoning was wrong.** `ticksToSeconds` rebuilds the double as `whole + remainder / 1e7`,
22
+ two roundings rather than one correctly-rounded division, so a legitimate file need not agree. A
23
+ sweep over header spellings an 8-byte field can hold showed an exact check refusing about one file
24
+ in a hundred.
25
+ - So the two populations were measured instead, and the tolerance sits between them. Over 7,729
26
+ spellings the worst disagreement for a MATCHED pair is **3.2e-16** relative; the closest two
27
+ genuinely DIFFERENT headers come is **1.1e-7**, and that is forced — eight ASCII bytes carry at
28
+ most seven significant digits, so two distinct durations differ by at least about one part in 10^7.
29
+ `1e-11` sits roughly 31,000x above the first and 11,000x below the second.
30
+ - The repository's distrust of epsilons — "a float tolerance is how a one-sample overlap becomes
31
+ invisible" — is about TICK comparisons, where exactness is available. Here it is not, and the
32
+ property test carries the sweep that justifies the number so it cannot quietly drift.
33
+
9
34
  ## 0.6.309
10
35
 
11
36
  - `biome check` now reports nothing at all. Two `info`-level findings had sat in the tree
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edfcore",
3
- "version": "0.6.309",
3
+ "version": "0.6.310",
4
4
  "description": "Modern, typed, zero-dependency reader for EDF, EDF+, BDF and BDF+ biosignal files. Works in browsers and Node with true random access.",
5
5
  "keywords": [
6
6
  "edf",
package/src/constants.ts CHANGED
@@ -93,4 +93,4 @@ export const SIGNAL_FIELD_BLOCK_OFFSETS = {
93
93
  } as const;
94
94
 
95
95
  /** Published package version. Kept in sync with package.json by a test. */
96
- export const VERSION = '0.6.309';
96
+ export const VERSION = '0.6.310';
@@ -42,10 +42,17 @@
42
42
  */
43
43
 
44
44
  import { TICKS_PER_SECOND } from './constants.js';
45
- import { secondsToTicks } from './tal/ticks.js';
45
+ import { secondsToTicks, ticksToSeconds } from './tal/ticks.js';
46
46
  import { describeValue } from './text/describe.js';
47
47
  import type { EdfSampleLocation, EdfSignal } from './types.js';
48
48
 
49
+ /**
50
+ * How far the implied sample rate may sit from the declared one before the two arguments are taken
51
+ * to be from different headers. See `assertGrid`: the noise floor for a matched pair is 3.2e-16
52
+ * relative and the floor for a genuine mismatch is 1.1e-7, both measured.
53
+ */
54
+ const GRID_RATE_TOLERANCE = 1e-11;
55
+
49
56
  function assertGrid(signal: EdfSignal, recordDurationTicks: bigint, call: string): void {
50
57
  /*
51
58
  * Both arguments, before either is read. `signal.kind` on a chunk signal — the per-signal shape a
@@ -99,6 +106,47 @@ function assertGrid(signal: EdfSignal, recordDurationTicks: bigint, call: string
99
106
  'Next: index by record with readRecords().',
100
107
  );
101
108
  }
109
+ /*
110
+ * And that the two arguments came from the SAME HEADER, which nothing above can see.
111
+ *
112
+ * This family takes the signal and the record duration separately, and only the caller pairs
113
+ * them. Mix them and the grid is silently wrong by the ratio of the two durations: a 10 Hz signal
114
+ * asked with another file's 2 s ticks answered `sampleIndex: 5` at t = 1 s where the truth is 10.
115
+ * No throw, no diagnostic, half the index. `decodeDigital` refuses the analogous bytes-against-
116
+ * records mismatch and says why — it "cannot tell which record a differently sized buffer begins
117
+ * at, so it will not guess".
118
+ *
119
+ * `sampleRateHz` is the only thing bridging them, and it is a float: the header parses
120
+ * `recordDurationSeconds` from the field independently of `recordDurationTicks`, so this compares
121
+ * two reconstructions of one decimal rather than two copies of a number.
122
+ *
123
+ * WHICH IS WHY THIS IS A TOLERANCE AND NOT AN EQUALITY. `ticksToSeconds` rebuilds the double as
124
+ * `whole + remainder / 1e7` — two roundings — so for a legitimate file it need not equal the
125
+ * parsed float. Measured over 7,729 header spellings an 8-byte field can hold, the worst
126
+ * disagreement for a MATCHED pair is 3.2e-16 relative, and an exact check refused about one file
127
+ * in a hundred. The closest two genuinely DIFFERENT headers come is 1.1e-7, which is forced: eight
128
+ * ASCII bytes carry at most seven significant digits, so two distinct durations differ by at least
129
+ * about one part in 10^7.
130
+ *
131
+ * 1e-11 sits roughly 31,000x above the noise and 11,000x below the signal. The repository's
132
+ * distrust of epsilons — "a float tolerance is how a one-sample overlap becomes invisible" — is
133
+ * about TICK comparisons, where exactness is available. Here it is not, and the two populations
134
+ * are eight orders of magnitude apart (0.6.310).
135
+ */
136
+ const declaredRate = signal.sampleRateHz;
137
+ if (declaredRate !== undefined && declaredRate > 0) {
138
+ const impliedRate = signal.samplesPerRecord / ticksToSeconds(recordDurationTicks);
139
+ if (Math.abs(impliedRate - declaredRate) > declaredRate * GRID_RATE_TOLERANCE) {
140
+ throw new RangeError(
141
+ `${call}(): signal ${signal.index} (${JSON.stringify(signal.label)}) samples at ` +
142
+ `${declaredRate} Hz, but ${ticksToSeconds(recordDurationTicks)} s per record puts its ` +
143
+ `${signal.samplesPerRecord} samples at ${impliedRate} Hz — so the signal and the record ` +
144
+ 'duration came from different headers, and the grid this would measure belongs to ' +
145
+ 'neither file. Next: pass header.recordDurationTicks from the same header the signal ' +
146
+ 'came out of.',
147
+ );
148
+ }
149
+ }
102
150
  }
103
151
 
104
152
  /**