edfcore 0.2.60 → 0.2.62

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 CHANGED
@@ -6,6 +6,36 @@ 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.61
10
+
11
+ - **Deprecated** `sampleIndexAt`, `sampleStartTicks` and `sampleStartSeconds`, which are renamed
12
+ to `gridSampleIndexAt`, `gridSampleStartTicks` and `gridSampleStartSeconds` in **0.3.0**. The
13
+ behaviour does not change and neither do the arguments — only the name, which never said which of
14
+ two different quantities it returns. Six releases of this project were spent on exactly that
15
+ confusion elsewhere, and the `grid` prefix is what stops the seventh.
16
+ - The tag is folded into each function's existing documentation rather than added as a second
17
+ comment above it, so an editor shows the original prose AND the replacement instead of replacing
18
+ one with the other.
19
+ - Nothing is removed here. An editor will point at the replacement a release before the rename
20
+ lands, and for a contiguous file the rename is the only thing that affects a caller.
21
+
22
+ ## 0.2.60
23
+
24
+ - **Added** `sampleAt`, `sampleStartTicksOf` and `sampleStartSecondsOf` — the recording-aware
25
+ counterpart to the sample-grid family, and the groundwork for 0.3.0.
26
+ - 0.2.32 documented why the existing three cannot be fixed in place: they take
27
+ `(signal, value, recordDurationTicks)`, so a gap is not in their arguments and no arithmetic
28
+ inside them could find one. These take the RECORDING. On a contiguous file they agree with the
29
+ grid functions exactly — asserted sample by sample — and on a discontinuous one they differ by
30
+ the gaps.
31
+ - `sampleAt` can return **`undefined`**, which is the answer the grid form structurally cannot
32
+ give: no sample exists at that instant, because it falls in a gap, before the recording, or after
33
+ it. `sampleIndexAt(signal, 5, d)` on a six-record file with a hole at 5 s names record 5; there
34
+ is no record 5 at that time.
35
+ - Both refuse a probed index on a file with gaps rather than guessing, the same rule `segmentAt`
36
+ follows. A round-trip test pins the pair together across the gap: the sample at a sample's start
37
+ is that sample, for every sample in the file.
38
+
9
39
  ## 0.2.59
10
40
 
11
41
  - **Added** `calib.rec` from edfplus.info — the last corpus the README named. It was written by
@@ -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.60";
112
+ export declare const VERSION = "0.2.62";
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.2.60';
82
+ export const VERSION = '0.2.62';
83
83
  //# sourceMappingURL=constants.js.map
package/dist/index.d.ts CHANGED
@@ -51,5 +51,6 @@ export { formatHeader } from './format-header.js';
51
51
  export { inspectEdf } from './inspect.js';
52
52
  export { openEdf, readAnnotations, readRecords, readWindow } from './recording.js';
53
53
  export { sampleIndexAt, sampleStartSeconds, sampleStartTicks, } from './sample-grid.js';
54
+ export { sampleAt, sampleStartSecondsOf, sampleStartTicksOf, } from './sample-locate.js';
54
55
  export { streamRecords } from './stream.js';
55
56
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -63,5 +63,6 @@ export { formatHeader } from './format-header.js';
63
63
  export { inspectEdf } from './inspect.js';
64
64
  export { openEdf, readAnnotations, readRecords, readWindow } from './recording.js';
65
65
  export { sampleIndexAt, sampleStartSeconds, sampleStartTicks, } from './sample-grid.js';
66
+ export { sampleAt, sampleStartSecondsOf, sampleStartTicksOf, } from './sample-locate.js';
66
67
  export { streamRecords } from './stream.js';
67
68
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AA2EH,OAAO,EACL,wBAAwB,EACxB,cAAc,EACd,uBAAuB,EACvB,QAAQ,EACR,cAAc,EACd,aAAa,EACb,eAAe,EACf,cAAc,EACd,UAAU,GACX,MAAM,aAAa,CAAC;AAErB,8EAA8E;AAC9E,YAAY;AACZ,8EAA8E;AAE9E,OAAO,EACL,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,sBAAsB,EACtB,gCAAgC,EAChC,gBAAgB,EAChB,OAAO,GACR,MAAM,gBAAgB,CAAC;AAExB,8EAA8E;AAC9E,2EAA2E;AAC3E,2CAA2C;AAC3C,8EAA8E;AAE9E,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACxF,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EACL,uBAAuB,EACvB,WAAW,EACX,SAAS,EACT,iBAAiB,EACjB,YAAY,GACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEnE,8EAA8E;AAC9E,yEAAyE;AACzE,2EAA2E;AAC3E,6CAA6C;AAC7C,8EAA8E;AAE9E,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,8EAA8E;AAC9E,uCAAuC;AACvC,8EAA8E;AAE9E,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,KAAK,EACL,SAAS,GACV,MAAM,mBAAmB,CAAC;AAE3B,8EAA8E;AAC9E,oBAAoB;AACpB,8EAA8E;AAE9E,OAAO,EACL,aAAa,EACb,sBAAsB,EACtB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC/E,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACnF,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AA2EH,OAAO,EACL,wBAAwB,EACxB,cAAc,EACd,uBAAuB,EACvB,QAAQ,EACR,cAAc,EACd,aAAa,EACb,eAAe,EACf,cAAc,EACd,UAAU,GACX,MAAM,aAAa,CAAC;AAErB,8EAA8E;AAC9E,YAAY;AACZ,8EAA8E;AAE9E,OAAO,EACL,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,sBAAsB,EACtB,gCAAgC,EAChC,gBAAgB,EAChB,OAAO,GACR,MAAM,gBAAgB,CAAC;AAExB,8EAA8E;AAC9E,2EAA2E;AAC3E,2CAA2C;AAC3C,8EAA8E;AAE9E,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACxF,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EACL,uBAAuB,EACvB,WAAW,EACX,SAAS,EACT,iBAAiB,EACjB,YAAY,GACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEnE,8EAA8E;AAC9E,yEAAyE;AACzE,2EAA2E;AAC3E,6CAA6C;AAC7C,8EAA8E;AAE9E,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,8EAA8E;AAC9E,uCAAuC;AACvC,8EAA8E;AAE9E,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,KAAK,EACL,SAAS,GACV,MAAM,mBAAmB,CAAC;AAE3B,8EAA8E;AAC9E,oBAAoB;AACpB,8EAA8E;AAE9E,OAAO,EACL,aAAa,EACb,sBAAsB,EACtB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC/E,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACnF,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,QAAQ,EACR,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
@@ -28,10 +28,16 @@
28
28
  * count, and are not being modest about it: the information is not in their arguments.
29
29
  *
30
30
  * So the contract is stated rather than guessed at. For a file that may be discontinuous, use
31
- * `index.locate(seconds)` for time to record, `segmentAt`/`gapAt` to find out whether an instant
32
- * has data at all, and `chunk.firstSampleIndex` for the sample index a read actually produced.
33
- * `contiguityOf(index)` answers which regime you are in. On a contiguous file — the common case,
34
- * and every plain EDF or EDF+C — these are exact and are what you want.
31
+ * `sampleAt` / `sampleStartTicksOf` / `sampleStartSecondsOf` from `sample-locate.ts`, which take
32
+ * the recording and can therefore see a gap. `contiguityOf(index)` answers which regime you are
33
+ * in. On a contiguous file — the common case, and every plain EDF or EDF+C — these are exact and
34
+ * are what you want.
35
+ *
36
+ * RENAMED IN 0.3.0. These three keep their behaviour and lose their misleading names: they become
37
+ * `gridSampleIndexAt`, `gridSampleStartTicks` and `gridSampleStartSeconds`. The `grid` prefix is
38
+ * the whole fix — the functions were never wrong, the names simply did not say which of two
39
+ * different quantities they returned, and six releases of this project were spent on exactly that
40
+ * confusion in other places. Nothing about the arithmetic changes.
35
41
  */
36
42
  import type { EdfSampleLocation, EdfSignal } from './types.js';
37
43
  /**
@@ -40,6 +46,11 @@ import type { EdfSampleLocation, EdfSignal } from './types.js';
40
46
  * Floor, not round: a sample covers the half-open interval from its own start to the next one's,
41
47
  * so the sample "at" a time is the one whose interval contains it. Rounding would return the
42
48
  * NEXT sample for anything past the halfway point, which puts a window boundary one sample late.
49
+ *
50
+ * @deprecated Renamed to `gridSampleIndexAt` in 0.3.0. The behaviour is unchanged — only the
51
+ * name, which never said which of two different quantities it returns. For a file that may
52
+ * have gaps, use `sampleAt` / `sampleStartTicksOf` / `sampleStartSecondsOf`, which take the
53
+ * recording and can therefore see one.
43
54
  */
44
55
  export declare function sampleIndexAt(signal: EdfSignal, seconds: number, recordDurationTicks: bigint): EdfSampleLocation;
45
56
  /**
@@ -54,8 +65,18 @@ export declare function sampleIndexAt(signal: EdfSignal, seconds: number, record
54
65
  * edfcore has. Truncating would return 23,437 — a tick that lies inside sample 0 — so
55
66
  * `sampleIndexAt` would send it straight back to the previous sample. Taking the first whole
56
67
  * tick at or after the exact start keeps the two functions inverse for every index.
68
+ *
69
+ * @deprecated Renamed to `gridSampleStartTicks` in 0.3.0. The behaviour is unchanged — only the
70
+ * name, which never said which of two different quantities it returns. For a file that may
71
+ * have gaps, use `sampleAt` / `sampleStartTicksOf` / `sampleStartSecondsOf`, which take the
72
+ * recording and can therefore see one.
57
73
  */
58
74
  export declare function sampleStartTicks(signal: EdfSignal, sampleIndex: number, recordDurationTicks: bigint): bigint;
59
- /** `sampleStartTicks` in seconds, for display. Compare ticks, not this. */
75
+ /** `sampleStartTicks` in seconds, for display. Compare ticks, not this. *
76
+ * @deprecated Renamed to `gridSampleStartSeconds` in 0.3.0. The behaviour is unchanged — only the
77
+ * name, which never said which of two different quantities it returns. For a file that may
78
+ * have gaps, use `sampleAt` / `sampleStartTicksOf` / `sampleStartSecondsOf`, which take the
79
+ * recording and can therefore see one.
80
+ */
60
81
  export declare function sampleStartSeconds(signal: EdfSignal, sampleIndex: number, recordDurationTicks: bigint): number;
61
82
  //# sourceMappingURL=sample-grid.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sample-grid.d.ts","sourceRoot":"","sources":["../src/sample-grid.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AA0B/D;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,MAAM,EACf,mBAAmB,EAAE,MAAM,GAC1B,iBAAiB,CAoBnB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,MAAM,EACnB,mBAAmB,EAAE,MAAM,GAC1B,MAAM,CAcR;AAED,2EAA2E;AAC3E,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,MAAM,EACnB,mBAAmB,EAAE,MAAM,GAC1B,MAAM,CAKR"}
1
+ {"version":3,"file":"sample-grid.d.ts","sourceRoot":"","sources":["../src/sample-grid.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AA0B/D;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,MAAM,EACf,mBAAmB,EAAE,MAAM,GAC1B,iBAAiB,CAoBnB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,MAAM,EACnB,mBAAmB,EAAE,MAAM,GAC1B,MAAM,CAcR;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,MAAM,EACnB,mBAAmB,EAAE,MAAM,GAC1B,MAAM,CAKR"}
@@ -28,10 +28,16 @@
28
28
  * count, and are not being modest about it: the information is not in their arguments.
29
29
  *
30
30
  * So the contract is stated rather than guessed at. For a file that may be discontinuous, use
31
- * `index.locate(seconds)` for time to record, `segmentAt`/`gapAt` to find out whether an instant
32
- * has data at all, and `chunk.firstSampleIndex` for the sample index a read actually produced.
33
- * `contiguityOf(index)` answers which regime you are in. On a contiguous file — the common case,
34
- * and every plain EDF or EDF+C — these are exact and are what you want.
31
+ * `sampleAt` / `sampleStartTicksOf` / `sampleStartSecondsOf` from `sample-locate.ts`, which take
32
+ * the recording and can therefore see a gap. `contiguityOf(index)` answers which regime you are
33
+ * in. On a contiguous file — the common case, and every plain EDF or EDF+C — these are exact and
34
+ * are what you want.
35
+ *
36
+ * RENAMED IN 0.3.0. These three keep their behaviour and lose their misleading names: they become
37
+ * `gridSampleIndexAt`, `gridSampleStartTicks` and `gridSampleStartSeconds`. The `grid` prefix is
38
+ * the whole fix — the functions were never wrong, the names simply did not say which of two
39
+ * different quantities they returned, and six releases of this project were spent on exactly that
40
+ * confusion in other places. Nothing about the arithmetic changes.
35
41
  */
36
42
  import { TICKS_PER_SECOND } from './constants.js';
37
43
  import { secondsToTicks } from './tal/ticks.js';
@@ -58,6 +64,11 @@ function assertGrid(signal, recordDurationTicks) {
58
64
  * Floor, not round: a sample covers the half-open interval from its own start to the next one's,
59
65
  * so the sample "at" a time is the one whose interval contains it. Rounding would return the
60
66
  * NEXT sample for anything past the halfway point, which puts a window boundary one sample late.
67
+ *
68
+ * @deprecated Renamed to `gridSampleIndexAt` in 0.3.0. The behaviour is unchanged — only the
69
+ * name, which never said which of two different quantities it returns. For a file that may
70
+ * have gaps, use `sampleAt` / `sampleStartTicksOf` / `sampleStartSecondsOf`, which take the
71
+ * recording and can therefore see one.
61
72
  */
62
73
  export function sampleIndexAt(signal, seconds, recordDurationTicks) {
63
74
  assertGrid(signal, recordDurationTicks);
@@ -90,6 +101,11 @@ export function sampleIndexAt(signal, seconds, recordDurationTicks) {
90
101
  * edfcore has. Truncating would return 23,437 — a tick that lies inside sample 0 — so
91
102
  * `sampleIndexAt` would send it straight back to the previous sample. Taking the first whole
92
103
  * tick at or after the exact start keeps the two functions inverse for every index.
104
+ *
105
+ * @deprecated Renamed to `gridSampleStartTicks` in 0.3.0. The behaviour is unchanged — only the
106
+ * name, which never said which of two different quantities it returns. For a file that may
107
+ * have gaps, use `sampleAt` / `sampleStartTicksOf` / `sampleStartSecondsOf`, which take the
108
+ * recording and can therefore see one.
93
109
  */
94
110
  export function sampleStartTicks(signal, sampleIndex, recordDurationTicks) {
95
111
  assertGrid(signal, recordDurationTicks);
@@ -105,7 +121,12 @@ export function sampleStartTicks(signal, sampleIndex, recordDurationTicks) {
105
121
  return quotient;
106
122
  return numerator > 0n ? quotient + 1n : quotient;
107
123
  }
108
- /** `sampleStartTicks` in seconds, for display. Compare ticks, not this. */
124
+ /** `sampleStartTicks` in seconds, for display. Compare ticks, not this. *
125
+ * @deprecated Renamed to `gridSampleStartSeconds` in 0.3.0. The behaviour is unchanged — only the
126
+ * name, which never said which of two different quantities it returns. For a file that may
127
+ * have gaps, use `sampleAt` / `sampleStartTicksOf` / `sampleStartSecondsOf`, which take the
128
+ * recording and can therefore see one.
129
+ */
109
130
  export function sampleStartSeconds(signal, sampleIndex, recordDurationTicks) {
110
131
  const ticks = sampleStartTicks(signal, sampleIndex, recordDurationTicks);
111
132
  const whole = ticks / TICKS_PER_SECOND;
@@ -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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"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Time and sample index, on the recording's own axis.
3
+ *
4
+ * Layer 7. The recording-aware counterpart to `sample-grid.ts`, and the reason it exists is stated
5
+ * plainly there: `sampleIndexAt`, `sampleStartTicks` and `sampleStartSeconds` take
6
+ * `(signal, value, recordDurationTicks)` — no index, no timeline — so a gap is not in their
7
+ * arguments and no arithmetic inside them could find one. They measure the signal's own SAMPLE
8
+ * GRID, which equals elapsed recording time only when the recording is contiguous.
9
+ *
10
+ * These two take the recording, so they can answer the question people actually mean. On a
11
+ * contiguous file they agree with the grid functions exactly. On an EDF+D file they differ by the
12
+ * gaps, and `sampleAt` can answer something the grid functions structurally cannot: that an
13
+ * instant has NO sample at all, because it falls in a hole.
14
+ *
15
+ * Both refuse a probed index on a file whose records do not cover its span, for the reason
16
+ * `segmentAt` does: `undefined` from `sampleAt` means "no sample exists here", and an index that
17
+ * has read record 0 and the last record cannot say that about anything in between. Merging "there
18
+ * is a gap here" with "nobody looked" is the confusion this whole area of the API avoids.
19
+ */
20
+ import type { EdfRecording, EdfSampleLocation } from './types.js';
21
+ /**
22
+ * The sample covering `seconds`, or `undefined` when no sample does.
23
+ *
24
+ * `undefined` is a real answer rather than a failure: on an EDF+D file an instant inside a gap has
25
+ * no sample, and so does any time before the recording starts or after it ends. That is the case
26
+ * `sampleIndexAt` cannot express — given only a signal and a record duration it always returns an
27
+ * index, even one past the end of the file.
28
+ *
29
+ * Floor, not round, and in exact integer arithmetic on ticks: a sample covers the half-open
30
+ * interval from its own start to the next one's, so the sample "at" a time is the one already
31
+ * running when that time arrives.
32
+ */
33
+ export declare function sampleAt(recording: EdfRecording, signalIndex: number, seconds: number): EdfSampleLocation | undefined;
34
+ /**
35
+ * When a sample starts, in exact ticks on the recording's axis.
36
+ *
37
+ * The inverse of `sampleAt`, and the recording-aware form of `sampleStartTicks`. On a contiguous
38
+ * file the two agree exactly; on an EDF+D file this one includes the gaps that precede the sample
39
+ * and `sampleStartTicks` does not.
40
+ *
41
+ * Rounds UP to a whole tick, as `sampleStartTicks` does: a sample boundary need not fall on one —
42
+ * 128 samples over 0.3 s puts sample 1 at 23,437.5 ticks — and truncating would return a tick
43
+ * lying inside the previous sample, which `sampleAt` would then map straight back to that
44
+ * previous sample.
45
+ */
46
+ export declare function sampleStartTicksOf(recording: EdfRecording, signalIndex: number, sampleIndex: number): bigint;
47
+ /** `sampleStartTicksOf` as float64 seconds. Compare with the ticks, never with this. */
48
+ export declare function sampleStartSecondsOf(recording: EdfRecording, signalIndex: number, sampleIndex: number): number;
49
+ //# sourceMappingURL=sample-locate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sample-locate.d.ts","sourceRoot":"","sources":["../src/sample-locate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAyB,MAAM,YAAY,CAAC;AAkFzF;;;;;;;;;;;GAWG;AACH,wBAAgB,QAAQ,CACtB,SAAS,EAAE,YAAY,EACvB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,GACd,iBAAiB,GAAG,SAAS,CAqC/B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,YAAY,EACvB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,MAAM,CA0BR;AAED,wFAAwF;AACxF,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,YAAY,EACvB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,MAAM,CAER"}
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Time and sample index, on the recording's own axis.
3
+ *
4
+ * Layer 7. The recording-aware counterpart to `sample-grid.ts`, and the reason it exists is stated
5
+ * plainly there: `sampleIndexAt`, `sampleStartTicks` and `sampleStartSeconds` take
6
+ * `(signal, value, recordDurationTicks)` — no index, no timeline — so a gap is not in their
7
+ * arguments and no arithmetic inside them could find one. They measure the signal's own SAMPLE
8
+ * GRID, which equals elapsed recording time only when the recording is contiguous.
9
+ *
10
+ * These two take the recording, so they can answer the question people actually mean. On a
11
+ * contiguous file they agree with the grid functions exactly. On an EDF+D file they differ by the
12
+ * gaps, and `sampleAt` can answer something the grid functions structurally cannot: that an
13
+ * instant has NO sample at all, because it falls in a hole.
14
+ *
15
+ * Both refuse a probed index on a file whose records do not cover its span, for the reason
16
+ * `segmentAt` does: `undefined` from `sampleAt` means "no sample exists here", and an index that
17
+ * has read record 0 and the last record cannot say that about anything in between. Merging "there
18
+ * is a gap here" with "nobody looked" is the confusion this whole area of the API avoids.
19
+ */
20
+ import { EdfChannelNotFoundError } from './errors.js';
21
+ import { segmentAt } from './record-index.js';
22
+ import { secondsToTicks, ticksToSeconds } from './tal/ticks.js';
23
+ /** `b` must be positive. Bigint `/` truncates toward zero, so negatives need the correction. */
24
+ function floorDiv(a, b) {
25
+ const quotient = a / b;
26
+ return a % b === 0n || a > 0n ? quotient : quotient - 1n;
27
+ }
28
+ function resolveSignal(recording, signalIndex, caller) {
29
+ const signal = recording.header.signals[signalIndex];
30
+ if (signal === undefined) {
31
+ throw new EdfChannelNotFoundError(`${caller}(): signalIndex ${signalIndex} is outside the ` +
32
+ `${recording.header.signals.length} signals this file declares. Next: pass an index from ` +
33
+ 'header.dataSignalIndices, or resolve one with getSignal(header, label).', {
34
+ selector: signalIndex,
35
+ availableLabels: recording.header.signals.map((s) => s.label),
36
+ });
37
+ }
38
+ if (signal.kind === 'annotations') {
39
+ throw new RangeError(`${caller}(): signal ${signalIndex} (${JSON.stringify(signal.label)}) is an annotations ` +
40
+ 'channel, whose region holds TAL text rather than samples, so it has no sample grid. ' +
41
+ 'Next: use onsetTicksFromFirstRecord on the annotations themselves.');
42
+ }
43
+ if (signal.samplesPerRecord <= 0) {
44
+ throw new RangeError(`${caller}(): signal ${signalIndex} (${JSON.stringify(signal.label)}) declares ` +
45
+ `${signal.samplesPerRecord} samples per record, so it has no sample grid to index. ` +
46
+ 'Next: check header.diagnostics for ZERO_SAMPLES_PER_RECORD.');
47
+ }
48
+ if (recording.header.recordDurationTicks <= 0n) {
49
+ throw new RangeError(`${caller}(): this file declares a record duration of zero, so records do not advance in ` +
50
+ 'time and no elapsed time maps to a sample. This is legal EDF and a scoring file relies ' +
51
+ 'on it. Next: index by record with readRecords().');
52
+ }
53
+ return signal;
54
+ }
55
+ /**
56
+ * The record a segment places at a given position, and that record's true start in ticks.
57
+ *
58
+ * `segment.startTicks` is already on the recording's axis, and records inside one segment are
59
+ * contiguous by construction, so this is exact.
60
+ */
61
+ function recordStartTicks(recording, segment, recordIndex) {
62
+ const duration = recording.header.recordDurationTicks;
63
+ if (segment === undefined)
64
+ return BigInt(recordIndex) * duration;
65
+ return segment.startTicks + BigInt(recordIndex - segment.records.start) * duration;
66
+ }
67
+ /** The segment holding a record, on a scanned index; `undefined` when the file is contiguous. */
68
+ function segmentOfRecord(recording, recordIndex) {
69
+ const segments = recording.index.segments;
70
+ if (segments === undefined)
71
+ return undefined;
72
+ for (const segment of segments) {
73
+ const first = segment.records.start;
74
+ if (recordIndex >= first && recordIndex < first + segment.records.count)
75
+ return segment;
76
+ }
77
+ return undefined;
78
+ }
79
+ /**
80
+ * Whether this recording needs a scanned index before a time can be located at all.
81
+ *
82
+ * A file whose records cover exactly its span is contiguous, and the nominal grid IS the true one
83
+ * — so a probed index is enough and no scan is demanded of the caller for nothing.
84
+ */
85
+ function requiresScan(recording) {
86
+ return recording.timeline.spanSeconds !== recording.timeline.coveredSeconds;
87
+ }
88
+ /**
89
+ * The sample covering `seconds`, or `undefined` when no sample does.
90
+ *
91
+ * `undefined` is a real answer rather than a failure: on an EDF+D file an instant inside a gap has
92
+ * no sample, and so does any time before the recording starts or after it ends. That is the case
93
+ * `sampleIndexAt` cannot express — given only a signal and a record duration it always returns an
94
+ * index, even one past the end of the file.
95
+ *
96
+ * Floor, not round, and in exact integer arithmetic on ticks: a sample covers the half-open
97
+ * interval from its own start to the next one's, so the sample "at" a time is the one already
98
+ * running when that time arrives.
99
+ */
100
+ export function sampleAt(recording, signalIndex, seconds) {
101
+ const signal = resolveSignal(recording, signalIndex, 'sampleAt');
102
+ if (!Number.isFinite(seconds)) {
103
+ throw new RangeError(`sampleAt(): seconds must be a finite number, received ${seconds}.`);
104
+ }
105
+ const duration = recording.header.recordDurationTicks;
106
+ const perRecord = BigInt(signal.samplesPerRecord);
107
+ const ticks = secondsToTicks(seconds);
108
+ if (!requiresScan(recording)) {
109
+ // Contiguous: the nominal grid is the true one, but the answer is still bounded by the file.
110
+ const recordIndex = Number(floorDiv(ticks, duration));
111
+ if (recordIndex < 0 || recordIndex >= recording.header.recordCount)
112
+ return undefined;
113
+ const withinTicks = ticks - BigInt(recordIndex) * duration;
114
+ const sampleWithinRecord = Number(floorDiv(withinTicks * perRecord, duration));
115
+ return {
116
+ sampleIndex: recordIndex * signal.samplesPerRecord + sampleWithinRecord,
117
+ recordIndex,
118
+ sampleWithinRecord,
119
+ };
120
+ }
121
+ // Discontinuous: `segmentAt` owns the "is there data here at all" question, and throws for a
122
+ // probed index rather than guessing.
123
+ const segment = segmentAt(recording.index, seconds);
124
+ if (segment === undefined)
125
+ return undefined;
126
+ const offsetTicks = ticks - segment.startTicks;
127
+ const recordIndex = segment.records.start + Number(floorDiv(offsetTicks, duration));
128
+ const withinTicks = offsetTicks - floorDiv(offsetTicks, duration) * duration;
129
+ const sampleWithinRecord = Number(floorDiv(withinTicks * perRecord, duration));
130
+ return {
131
+ sampleIndex: recordIndex * signal.samplesPerRecord + sampleWithinRecord,
132
+ recordIndex,
133
+ sampleWithinRecord,
134
+ };
135
+ }
136
+ /**
137
+ * When a sample starts, in exact ticks on the recording's axis.
138
+ *
139
+ * The inverse of `sampleAt`, and the recording-aware form of `sampleStartTicks`. On a contiguous
140
+ * file the two agree exactly; on an EDF+D file this one includes the gaps that precede the sample
141
+ * and `sampleStartTicks` does not.
142
+ *
143
+ * Rounds UP to a whole tick, as `sampleStartTicks` does: a sample boundary need not fall on one —
144
+ * 128 samples over 0.3 s puts sample 1 at 23,437.5 ticks — and truncating would return a tick
145
+ * lying inside the previous sample, which `sampleAt` would then map straight back to that
146
+ * previous sample.
147
+ */
148
+ export function sampleStartTicksOf(recording, signalIndex, sampleIndex) {
149
+ const signal = resolveSignal(recording, signalIndex, 'sampleStartTicksOf');
150
+ if (!Number.isSafeInteger(sampleIndex)) {
151
+ throw new RangeError(`sampleStartTicksOf(): sampleIndex must be a whole number, received ${sampleIndex}.`);
152
+ }
153
+ const perRecord = signal.samplesPerRecord;
154
+ const duration = recording.header.recordDurationTicks;
155
+ const recordIndex = Math.floor(sampleIndex / perRecord);
156
+ const within = BigInt(sampleIndex - recordIndex * perRecord);
157
+ if (requiresScan(recording) && recording.index.segments === undefined) {
158
+ throw new RangeError('sampleStartTicksOf(): this file has gaps and its index has not been scanned, so the true ' +
159
+ 'start of a record after a gap is not known. Next: await buildRecordIndex(recording) and ' +
160
+ 'read the result into the recording.');
161
+ }
162
+ const start = recordStartTicks(recording, segmentOfRecord(recording, recordIndex), recordIndex);
163
+ const numerator = within * duration;
164
+ const offset = numerator / BigInt(perRecord);
165
+ // Ceil, matching `sampleStartTicks`.
166
+ return start + (numerator % BigInt(perRecord) === 0n ? offset : offset + 1n);
167
+ }
168
+ /** `sampleStartTicksOf` as float64 seconds. Compare with the ticks, never with this. */
169
+ export function sampleStartSecondsOf(recording, signalIndex, sampleIndex) {
170
+ return ticksToSeconds(sampleStartTicksOf(recording, signalIndex, sampleIndex));
171
+ }
172
+ //# sourceMappingURL=sample-locate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sample-locate.js","sourceRoot":"","sources":["../src/sample-locate.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edfcore",
3
- "version": "0.2.60",
3
+ "version": "0.2.62",
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.2.60';
96
+ export const VERSION = '0.2.62';
package/src/index.ts CHANGED
@@ -194,4 +194,9 @@ export {
194
194
  sampleStartSeconds,
195
195
  sampleStartTicks,
196
196
  } from './sample-grid.js';
197
+ export {
198
+ sampleAt,
199
+ sampleStartSecondsOf,
200
+ sampleStartTicksOf,
201
+ } from './sample-locate.js';
197
202
  export { streamRecords } from './stream.js';
@@ -28,10 +28,16 @@
28
28
  * count, and are not being modest about it: the information is not in their arguments.
29
29
  *
30
30
  * So the contract is stated rather than guessed at. For a file that may be discontinuous, use
31
- * `index.locate(seconds)` for time to record, `segmentAt`/`gapAt` to find out whether an instant
32
- * has data at all, and `chunk.firstSampleIndex` for the sample index a read actually produced.
33
- * `contiguityOf(index)` answers which regime you are in. On a contiguous file — the common case,
34
- * and every plain EDF or EDF+C — these are exact and are what you want.
31
+ * `sampleAt` / `sampleStartTicksOf` / `sampleStartSecondsOf` from `sample-locate.ts`, which take
32
+ * the recording and can therefore see a gap. `contiguityOf(index)` answers which regime you are
33
+ * in. On a contiguous file — the common case, and every plain EDF or EDF+C — these are exact and
34
+ * are what you want.
35
+ *
36
+ * RENAMED IN 0.3.0. These three keep their behaviour and lose their misleading names: they become
37
+ * `gridSampleIndexAt`, `gridSampleStartTicks` and `gridSampleStartSeconds`. The `grid` prefix is
38
+ * the whole fix — the functions were never wrong, the names simply did not say which of two
39
+ * different quantities they returned, and six releases of this project were spent on exactly that
40
+ * confusion in other places. Nothing about the arithmetic changes.
35
41
  */
36
42
 
37
43
  import { TICKS_PER_SECOND } from './constants.js';
@@ -68,6 +74,11 @@ function assertGrid(signal: EdfSignal, recordDurationTicks: bigint): void {
68
74
  * Floor, not round: a sample covers the half-open interval from its own start to the next one's,
69
75
  * so the sample "at" a time is the one whose interval contains it. Rounding would return the
70
76
  * NEXT sample for anything past the halfway point, which puts a window boundary one sample late.
77
+ *
78
+ * @deprecated Renamed to `gridSampleIndexAt` in 0.3.0. The behaviour is unchanged — only the
79
+ * name, which never said which of two different quantities it returns. For a file that may
80
+ * have gaps, use `sampleAt` / `sampleStartTicksOf` / `sampleStartSecondsOf`, which take the
81
+ * recording and can therefore see one.
71
82
  */
72
83
  export function sampleIndexAt(
73
84
  signal: EdfSignal,
@@ -107,6 +118,11 @@ export function sampleIndexAt(
107
118
  * edfcore has. Truncating would return 23,437 — a tick that lies inside sample 0 — so
108
119
  * `sampleIndexAt` would send it straight back to the previous sample. Taking the first whole
109
120
  * tick at or after the exact start keeps the two functions inverse for every index.
121
+ *
122
+ * @deprecated Renamed to `gridSampleStartTicks` in 0.3.0. The behaviour is unchanged — only the
123
+ * name, which never said which of two different quantities it returns. For a file that may
124
+ * have gaps, use `sampleAt` / `sampleStartTicksOf` / `sampleStartSecondsOf`, which take the
125
+ * recording and can therefore see one.
110
126
  */
111
127
  export function sampleStartTicks(
112
128
  signal: EdfSignal,
@@ -128,7 +144,12 @@ export function sampleStartTicks(
128
144
  return numerator > 0n ? quotient + 1n : quotient;
129
145
  }
130
146
 
131
- /** `sampleStartTicks` in seconds, for display. Compare ticks, not this. */
147
+ /** `sampleStartTicks` in seconds, for display. Compare ticks, not this. *
148
+ * @deprecated Renamed to `gridSampleStartSeconds` in 0.3.0. The behaviour is unchanged — only the
149
+ * name, which never said which of two different quantities it returns. For a file that may
150
+ * have gaps, use `sampleAt` / `sampleStartTicksOf` / `sampleStartSecondsOf`, which take the
151
+ * recording and can therefore see one.
152
+ */
132
153
  export function sampleStartSeconds(
133
154
  signal: EdfSignal,
134
155
  sampleIndex: number,
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Time and sample index, on the recording's own axis.
3
+ *
4
+ * Layer 7. The recording-aware counterpart to `sample-grid.ts`, and the reason it exists is stated
5
+ * plainly there: `sampleIndexAt`, `sampleStartTicks` and `sampleStartSeconds` take
6
+ * `(signal, value, recordDurationTicks)` — no index, no timeline — so a gap is not in their
7
+ * arguments and no arithmetic inside them could find one. They measure the signal's own SAMPLE
8
+ * GRID, which equals elapsed recording time only when the recording is contiguous.
9
+ *
10
+ * These two take the recording, so they can answer the question people actually mean. On a
11
+ * contiguous file they agree with the grid functions exactly. On an EDF+D file they differ by the
12
+ * gaps, and `sampleAt` can answer something the grid functions structurally cannot: that an
13
+ * instant has NO sample at all, because it falls in a hole.
14
+ *
15
+ * Both refuse a probed index on a file whose records do not cover its span, for the reason
16
+ * `segmentAt` does: `undefined` from `sampleAt` means "no sample exists here", and an index that
17
+ * has read record 0 and the last record cannot say that about anything in between. Merging "there
18
+ * is a gap here" with "nobody looked" is the confusion this whole area of the API avoids.
19
+ */
20
+
21
+ import { EdfChannelNotFoundError } from './errors.js';
22
+ import { segmentAt } from './record-index.js';
23
+ import { secondsToTicks, ticksToSeconds } from './tal/ticks.js';
24
+ import type { EdfRecording, EdfSampleLocation, EdfSegment, EdfSignal } from './types.js';
25
+
26
+ /** `b` must be positive. Bigint `/` truncates toward zero, so negatives need the correction. */
27
+ function floorDiv(a: bigint, b: bigint): bigint {
28
+ const quotient = a / b;
29
+ return a % b === 0n || a > 0n ? quotient : quotient - 1n;
30
+ }
31
+
32
+ function resolveSignal(recording: EdfRecording, signalIndex: number, caller: string): EdfSignal {
33
+ const signal = recording.header.signals[signalIndex];
34
+ if (signal === undefined) {
35
+ throw new EdfChannelNotFoundError(
36
+ `${caller}(): signalIndex ${signalIndex} is outside the ` +
37
+ `${recording.header.signals.length} signals this file declares. Next: pass an index from ` +
38
+ 'header.dataSignalIndices, or resolve one with getSignal(header, label).',
39
+ {
40
+ selector: signalIndex,
41
+ availableLabels: recording.header.signals.map((s) => s.label),
42
+ },
43
+ );
44
+ }
45
+ if (signal.kind === 'annotations') {
46
+ throw new RangeError(
47
+ `${caller}(): signal ${signalIndex} (${JSON.stringify(signal.label)}) is an annotations ` +
48
+ 'channel, whose region holds TAL text rather than samples, so it has no sample grid. ' +
49
+ 'Next: use onsetTicksFromFirstRecord on the annotations themselves.',
50
+ );
51
+ }
52
+ if (signal.samplesPerRecord <= 0) {
53
+ throw new RangeError(
54
+ `${caller}(): signal ${signalIndex} (${JSON.stringify(signal.label)}) declares ` +
55
+ `${signal.samplesPerRecord} samples per record, so it has no sample grid to index. ` +
56
+ 'Next: check header.diagnostics for ZERO_SAMPLES_PER_RECORD.',
57
+ );
58
+ }
59
+ if (recording.header.recordDurationTicks <= 0n) {
60
+ throw new RangeError(
61
+ `${caller}(): this file declares a record duration of zero, so records do not advance in ` +
62
+ 'time and no elapsed time maps to a sample. This is legal EDF and a scoring file relies ' +
63
+ 'on it. Next: index by record with readRecords().',
64
+ );
65
+ }
66
+ return signal;
67
+ }
68
+
69
+ /**
70
+ * The record a segment places at a given position, and that record's true start in ticks.
71
+ *
72
+ * `segment.startTicks` is already on the recording's axis, and records inside one segment are
73
+ * contiguous by construction, so this is exact.
74
+ */
75
+ function recordStartTicks(
76
+ recording: EdfRecording,
77
+ segment: EdfSegment | undefined,
78
+ recordIndex: number,
79
+ ): bigint {
80
+ const duration = recording.header.recordDurationTicks;
81
+ if (segment === undefined) return BigInt(recordIndex) * duration;
82
+ return segment.startTicks + BigInt(recordIndex - segment.records.start) * duration;
83
+ }
84
+
85
+ /** The segment holding a record, on a scanned index; `undefined` when the file is contiguous. */
86
+ function segmentOfRecord(recording: EdfRecording, recordIndex: number): EdfSegment | undefined {
87
+ const segments = recording.index.segments;
88
+ if (segments === undefined) return undefined;
89
+ for (const segment of segments) {
90
+ const first = segment.records.start;
91
+ if (recordIndex >= first && recordIndex < first + segment.records.count) return segment;
92
+ }
93
+ return undefined;
94
+ }
95
+
96
+ /**
97
+ * Whether this recording needs a scanned index before a time can be located at all.
98
+ *
99
+ * A file whose records cover exactly its span is contiguous, and the nominal grid IS the true one
100
+ * — so a probed index is enough and no scan is demanded of the caller for nothing.
101
+ */
102
+ function requiresScan(recording: EdfRecording): boolean {
103
+ return recording.timeline.spanSeconds !== recording.timeline.coveredSeconds;
104
+ }
105
+
106
+ /**
107
+ * The sample covering `seconds`, or `undefined` when no sample does.
108
+ *
109
+ * `undefined` is a real answer rather than a failure: on an EDF+D file an instant inside a gap has
110
+ * no sample, and so does any time before the recording starts or after it ends. That is the case
111
+ * `sampleIndexAt` cannot express — given only a signal and a record duration it always returns an
112
+ * index, even one past the end of the file.
113
+ *
114
+ * Floor, not round, and in exact integer arithmetic on ticks: a sample covers the half-open
115
+ * interval from its own start to the next one's, so the sample "at" a time is the one already
116
+ * running when that time arrives.
117
+ */
118
+ export function sampleAt(
119
+ recording: EdfRecording,
120
+ signalIndex: number,
121
+ seconds: number,
122
+ ): EdfSampleLocation | undefined {
123
+ const signal = resolveSignal(recording, signalIndex, 'sampleAt');
124
+ if (!Number.isFinite(seconds)) {
125
+ throw new RangeError(`sampleAt(): seconds must be a finite number, received ${seconds}.`);
126
+ }
127
+
128
+ const duration = recording.header.recordDurationTicks;
129
+ const perRecord = BigInt(signal.samplesPerRecord);
130
+ const ticks = secondsToTicks(seconds);
131
+
132
+ if (!requiresScan(recording)) {
133
+ // Contiguous: the nominal grid is the true one, but the answer is still bounded by the file.
134
+ const recordIndex = Number(floorDiv(ticks, duration));
135
+ if (recordIndex < 0 || recordIndex >= recording.header.recordCount) return undefined;
136
+ const withinTicks = ticks - BigInt(recordIndex) * duration;
137
+ const sampleWithinRecord = Number(floorDiv(withinTicks * perRecord, duration));
138
+ return {
139
+ sampleIndex: recordIndex * signal.samplesPerRecord + sampleWithinRecord,
140
+ recordIndex,
141
+ sampleWithinRecord,
142
+ };
143
+ }
144
+
145
+ // Discontinuous: `segmentAt` owns the "is there data here at all" question, and throws for a
146
+ // probed index rather than guessing.
147
+ const segment = segmentAt(recording.index, seconds);
148
+ if (segment === undefined) return undefined;
149
+
150
+ const offsetTicks = ticks - segment.startTicks;
151
+ const recordIndex = segment.records.start + Number(floorDiv(offsetTicks, duration));
152
+ const withinTicks = offsetTicks - floorDiv(offsetTicks, duration) * duration;
153
+ const sampleWithinRecord = Number(floorDiv(withinTicks * perRecord, duration));
154
+ return {
155
+ sampleIndex: recordIndex * signal.samplesPerRecord + sampleWithinRecord,
156
+ recordIndex,
157
+ sampleWithinRecord,
158
+ };
159
+ }
160
+
161
+ /**
162
+ * When a sample starts, in exact ticks on the recording's axis.
163
+ *
164
+ * The inverse of `sampleAt`, and the recording-aware form of `sampleStartTicks`. On a contiguous
165
+ * file the two agree exactly; on an EDF+D file this one includes the gaps that precede the sample
166
+ * and `sampleStartTicks` does not.
167
+ *
168
+ * Rounds UP to a whole tick, as `sampleStartTicks` does: a sample boundary need not fall on one —
169
+ * 128 samples over 0.3 s puts sample 1 at 23,437.5 ticks — and truncating would return a tick
170
+ * lying inside the previous sample, which `sampleAt` would then map straight back to that
171
+ * previous sample.
172
+ */
173
+ export function sampleStartTicksOf(
174
+ recording: EdfRecording,
175
+ signalIndex: number,
176
+ sampleIndex: number,
177
+ ): bigint {
178
+ const signal = resolveSignal(recording, signalIndex, 'sampleStartTicksOf');
179
+ if (!Number.isSafeInteger(sampleIndex)) {
180
+ throw new RangeError(
181
+ `sampleStartTicksOf(): sampleIndex must be a whole number, received ${sampleIndex}.`,
182
+ );
183
+ }
184
+
185
+ const perRecord = signal.samplesPerRecord;
186
+ const duration = recording.header.recordDurationTicks;
187
+ const recordIndex = Math.floor(sampleIndex / perRecord);
188
+ const within = BigInt(sampleIndex - recordIndex * perRecord);
189
+
190
+ if (requiresScan(recording) && recording.index.segments === undefined) {
191
+ throw new RangeError(
192
+ 'sampleStartTicksOf(): this file has gaps and its index has not been scanned, so the true ' +
193
+ 'start of a record after a gap is not known. Next: await buildRecordIndex(recording) and ' +
194
+ 'read the result into the recording.',
195
+ );
196
+ }
197
+
198
+ const start = recordStartTicks(recording, segmentOfRecord(recording, recordIndex), recordIndex);
199
+ const numerator = within * duration;
200
+ const offset = numerator / BigInt(perRecord);
201
+ // Ceil, matching `sampleStartTicks`.
202
+ return start + (numerator % BigInt(perRecord) === 0n ? offset : offset + 1n);
203
+ }
204
+
205
+ /** `sampleStartTicksOf` as float64 seconds. Compare with the ticks, never with this. */
206
+ export function sampleStartSecondsOf(
207
+ recording: EdfRecording,
208
+ signalIndex: number,
209
+ sampleIndex: number,
210
+ ): number {
211
+ return ticksToSeconds(sampleStartTicksOf(recording, signalIndex, sampleIndex));
212
+ }