edfcore 0.1.0

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.
Files changed (188) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +319 -0
  3. package/dist/bytes/latin1.d.ts +43 -0
  4. package/dist/bytes/latin1.d.ts.map +1 -0
  5. package/dist/bytes/latin1.js +84 -0
  6. package/dist/bytes/latin1.js.map +1 -0
  7. package/dist/bytes/numbers.d.ts +45 -0
  8. package/dist/bytes/numbers.d.ts.map +1 -0
  9. package/dist/bytes/numbers.js +87 -0
  10. package/dist/bytes/numbers.js.map +1 -0
  11. package/dist/bytes/view.d.ts +27 -0
  12. package/dist/bytes/view.d.ts.map +1 -0
  13. package/dist/bytes/view.js +53 -0
  14. package/dist/bytes/view.js.map +1 -0
  15. package/dist/constants.d.ts +113 -0
  16. package/dist/constants.d.ts.map +1 -0
  17. package/dist/constants.js +83 -0
  18. package/dist/constants.js.map +1 -0
  19. package/dist/decode/digital.d.ts +49 -0
  20. package/dist/decode/digital.d.ts.map +1 -0
  21. package/dist/decode/digital.js +180 -0
  22. package/dist/decode/digital.js.map +1 -0
  23. package/dist/decode/physical.d.ts +37 -0
  24. package/dist/decode/physical.d.ts.map +1 -0
  25. package/dist/decode/physical.js +178 -0
  26. package/dist/decode/physical.js.map +1 -0
  27. package/dist/diagnostics/codes.d.ts +38 -0
  28. package/dist/diagnostics/codes.d.ts.map +1 -0
  29. package/dist/diagnostics/codes.js +134 -0
  30. package/dist/diagnostics/codes.js.map +1 -0
  31. package/dist/diagnostics/collector.d.ts +76 -0
  32. package/dist/diagnostics/collector.d.ts.map +1 -0
  33. package/dist/diagnostics/collector.js +95 -0
  34. package/dist/diagnostics/collector.js.map +1 -0
  35. package/dist/diagnostics/format.d.ts +21 -0
  36. package/dist/diagnostics/format.d.ts.map +1 -0
  37. package/dist/diagnostics/format.js +125 -0
  38. package/dist/diagnostics/format.js.map +1 -0
  39. package/dist/errors.d.ts +143 -0
  40. package/dist/errors.d.ts.map +1 -0
  41. package/dist/errors.js +135 -0
  42. package/dist/errors.js.map +1 -0
  43. package/dist/header/dates.d.ts +101 -0
  44. package/dist/header/dates.d.ts.map +1 -0
  45. package/dist/header/dates.js +378 -0
  46. package/dist/header/dates.js.map +1 -0
  47. package/dist/header/fields.d.ts +105 -0
  48. package/dist/header/fields.d.ts.map +1 -0
  49. package/dist/header/fields.js +363 -0
  50. package/dist/header/fields.js.map +1 -0
  51. package/dist/header/identification.d.ts +47 -0
  52. package/dist/header/identification.d.ts.map +1 -0
  53. package/dist/header/identification.js +156 -0
  54. package/dist/header/identification.js.map +1 -0
  55. package/dist/header/lookup.d.ts +33 -0
  56. package/dist/header/lookup.d.ts.map +1 -0
  57. package/dist/header/lookup.js +67 -0
  58. package/dist/header/lookup.js.map +1 -0
  59. package/dist/header/parse.d.ts +32 -0
  60. package/dist/header/parse.d.ts.map +1 -0
  61. package/dist/header/parse.js +319 -0
  62. package/dist/header/parse.js.map +1 -0
  63. package/dist/header/scale.d.ts +68 -0
  64. package/dist/header/scale.d.ts.map +1 -0
  65. package/dist/header/scale.js +154 -0
  66. package/dist/header/scale.js.map +1 -0
  67. package/dist/header/signals.d.ts +64 -0
  68. package/dist/header/signals.d.ts.map +1 -0
  69. package/dist/header/signals.js +374 -0
  70. package/dist/header/signals.js.map +1 -0
  71. package/dist/header/variant.d.ts +67 -0
  72. package/dist/header/variant.d.ts.map +1 -0
  73. package/dist/header/variant.js +180 -0
  74. package/dist/header/variant.js.map +1 -0
  75. package/dist/index.d.ts +46 -0
  76. package/dist/index.d.ts.map +1 -0
  77. package/dist/index.js +59 -0
  78. package/dist/index.js.map +1 -0
  79. package/dist/inspect.d.ts +31 -0
  80. package/dist/inspect.d.ts.map +1 -0
  81. package/dist/inspect.js +165 -0
  82. package/dist/inspect.js.map +1 -0
  83. package/dist/io/blob.d.ts +14 -0
  84. package/dist/io/blob.d.ts.map +1 -0
  85. package/dist/io/blob.js +29 -0
  86. package/dist/io/blob.js.map +1 -0
  87. package/dist/io/bytes.d.ts +11 -0
  88. package/dist/io/bytes.d.ts.map +1 -0
  89. package/dist/io/bytes.js +26 -0
  90. package/dist/io/bytes.js.map +1 -0
  91. package/dist/io/cached.d.ts +19 -0
  92. package/dist/io/cached.d.ts.map +1 -0
  93. package/dist/io/cached.js +129 -0
  94. package/dist/io/cached.js.map +1 -0
  95. package/dist/io/http.d.ts +20 -0
  96. package/dist/io/http.d.ts.map +1 -0
  97. package/dist/io/http.js +225 -0
  98. package/dist/io/http.js.map +1 -0
  99. package/dist/io/read.d.ts +42 -0
  100. package/dist/io/read.d.ts.map +1 -0
  101. package/dist/io/read.js +119 -0
  102. package/dist/io/read.js.map +1 -0
  103. package/dist/io/source.d.ts +33 -0
  104. package/dist/io/source.d.ts.map +1 -0
  105. package/dist/io/source.js +72 -0
  106. package/dist/io/source.js.map +1 -0
  107. package/dist/node.d.ts +70 -0
  108. package/dist/node.d.ts.map +1 -0
  109. package/dist/node.js +109 -0
  110. package/dist/node.js.map +1 -0
  111. package/dist/record-index.d.ts +58 -0
  112. package/dist/record-index.d.ts.map +1 -0
  113. package/dist/record-index.js +290 -0
  114. package/dist/record-index.js.map +1 -0
  115. package/dist/recording.d.ts +57 -0
  116. package/dist/recording.d.ts.map +1 -0
  117. package/dist/recording.js +178 -0
  118. package/dist/recording.js.map +1 -0
  119. package/dist/tal/annotations.d.ts +36 -0
  120. package/dist/tal/annotations.d.ts.map +1 -0
  121. package/dist/tal/annotations.js +437 -0
  122. package/dist/tal/annotations.js.map +1 -0
  123. package/dist/tal/grammar.d.ts +111 -0
  124. package/dist/tal/grammar.d.ts.map +1 -0
  125. package/dist/tal/grammar.js +324 -0
  126. package/dist/tal/grammar.js.map +1 -0
  127. package/dist/tal/ticks.d.ts +69 -0
  128. package/dist/tal/ticks.d.ts.map +1 -0
  129. package/dist/tal/ticks.js +137 -0
  130. package/dist/tal/ticks.js.map +1 -0
  131. package/dist/time/segments.d.ts +33 -0
  132. package/dist/time/segments.d.ts.map +1 -0
  133. package/dist/time/segments.js +87 -0
  134. package/dist/time/segments.js.map +1 -0
  135. package/dist/time/timeline.d.ts +62 -0
  136. package/dist/time/timeline.d.ts.map +1 -0
  137. package/dist/time/timeline.js +227 -0
  138. package/dist/time/timeline.js.map +1 -0
  139. package/dist/time/window.d.ts +54 -0
  140. package/dist/time/window.d.ts.map +1 -0
  141. package/dist/time/window.js +232 -0
  142. package/dist/time/window.js.map +1 -0
  143. package/dist/types.d.ts +409 -0
  144. package/dist/types.d.ts.map +1 -0
  145. package/dist/types.js +16 -0
  146. package/dist/types.js.map +1 -0
  147. package/dist/validate.d.ts +53 -0
  148. package/dist/validate.d.ts.map +1 -0
  149. package/dist/validate.js +483 -0
  150. package/dist/validate.js.map +1 -0
  151. package/package.json +77 -0
  152. package/src/bytes/latin1.ts +87 -0
  153. package/src/bytes/numbers.ts +115 -0
  154. package/src/bytes/view.ts +59 -0
  155. package/src/constants.ts +96 -0
  156. package/src/decode/digital.ts +258 -0
  157. package/src/decode/physical.ts +235 -0
  158. package/src/diagnostics/codes.ts +209 -0
  159. package/src/diagnostics/collector.ts +129 -0
  160. package/src/diagnostics/format.ts +143 -0
  161. package/src/errors.ts +208 -0
  162. package/src/header/dates.ts +453 -0
  163. package/src/header/fields.ts +455 -0
  164. package/src/header/identification.ts +196 -0
  165. package/src/header/lookup.ts +81 -0
  166. package/src/header/parse.ts +398 -0
  167. package/src/header/scale.ts +268 -0
  168. package/src/header/signals.ts +550 -0
  169. package/src/header/variant.ts +239 -0
  170. package/src/index.ts +151 -0
  171. package/src/inspect.ts +181 -0
  172. package/src/io/blob.ts +31 -0
  173. package/src/io/bytes.ts +29 -0
  174. package/src/io/cached.ts +136 -0
  175. package/src/io/http.ts +299 -0
  176. package/src/io/read.ts +152 -0
  177. package/src/io/source.ts +87 -0
  178. package/src/node.ts +138 -0
  179. package/src/record-index.ts +389 -0
  180. package/src/recording.ts +234 -0
  181. package/src/tal/annotations.ts +574 -0
  182. package/src/tal/grammar.ts +540 -0
  183. package/src/tal/ticks.ts +172 -0
  184. package/src/time/segments.ts +109 -0
  185. package/src/time/timeline.ts +290 -0
  186. package/src/time/window.ts +304 -0
  187. package/src/types.ts +490 -0
  188. package/src/validate.ts +610 -0
@@ -0,0 +1,290 @@
1
+ /**
2
+ * The record index: two probes at open, one targeted probe on demand, a full scan only if asked.
3
+ *
4
+ * Layer 6. This module owns the I/O STRATEGY for record onsets and nothing else — what a valid
5
+ * timeline is belongs to `time/timeline.ts`, what a timekeeping TAL is belongs to
6
+ * `tal/annotations.ts`, and segmentation belongs to `time/segments.ts`. Every onset that reaches
7
+ * those modules from here came out of `decodeAnnotations`, so the "first TAL of the first
8
+ * annotation signal" rule has exactly one implementation.
9
+ *
10
+ * Cost is the design constraint. Opening a million-record EDF+D over HTTP must not read the file,
11
+ * so:
12
+ *
13
+ * - a file with no annotation signal is probed ZERO times: without a timekeeping TAL there is no
14
+ * per-record onset on disk, and record `r` starts at `r * recordDuration` by definition;
15
+ * - otherwise `buildTimeline` probes exactly two records, the first and the last, which detects
16
+ * any NET drift of the timeline for two reads. It is not a proof of contiguity, and
17
+ * `time/timeline.ts` says so in the diagnostic it emits;
18
+ * - `onsetTicks(r)` reads that ONE record and memoises the answer, so `locate()` costs
19
+ * O(log recordCount) reads and a second `locate()` nearby costs almost none;
20
+ * - `buildRecordIndex()` is the only function here that touches every record, it is chunked so
21
+ * memory stays bounded whatever the file size, and it is never called implicitly.
22
+ *
23
+ * A probe reads a whole data record rather than just the annotation signal's region. That is
24
+ * decision 7 of the design — the unit of I/O is the record range, never the channel range — and
25
+ * it is also what lets `decodeAnnotations` own the timekeeping rule: it requires the record's
26
+ * full bytes, and reading less would mean reimplementing that rule here.
27
+ */
28
+ import { DEFAULT_MAX_MATERIALIZE_BYTES } from './constants.js';
29
+ import { EdfRangeError } from './errors.js';
30
+ import { readRecordBytes } from './io/read.js';
31
+ import { decodeAnnotations } from './tal/annotations.js';
32
+ import { secondsToTicks, ticksToSeconds } from './tal/ticks.js';
33
+ import { buildSegmentation } from './time/segments.js';
34
+ import { assertMonotonicOnsetArray, assertMonotonicOnsets, buildTimelineFromProbes, } from './time/timeline.js';
35
+ /**
36
+ * How much a full scan is allowed to hold at once, independently of `maxMaterializeBytes`.
37
+ *
38
+ * The materialisation budget is a ceiling on what one call may allocate; this is the working set
39
+ * of a traversal that could otherwise read a 13 GB BDF into a single buffer just because the
40
+ * budget permitted it. A scan is sequential, so a bigger block buys nothing.
41
+ */
42
+ const SCAN_BLOCK_TARGET_BYTES = 4 * 1024 * 1024;
43
+ /** Records per chunk of a full traversal: bounded memory, and never fewer than one record. */
44
+ export function scanChunkRecords(header, maxMaterializeBytes) {
45
+ const budget = Math.min(SCAN_BLOCK_TARGET_BYTES, maxMaterializeBytes ?? DEFAULT_MAX_MATERIALIZE_BYTES);
46
+ if (header.recordByteLength <= 0)
47
+ return 1;
48
+ return Math.max(1, Math.floor(budget / header.recordByteLength));
49
+ }
50
+ /**
51
+ * The onset a record has when the file carries no timekeeping TAL to read.
52
+ *
53
+ * Not a fallback for a missing TAL — `decodeAnnotations` owns that case and reports it — but the
54
+ * definition for a plain EDF or BDF file, where record onsets are not stored at all.
55
+ */
56
+ function nominalOnsetTicks(header, recordIndex) {
57
+ return BigInt(recordIndex) * header.recordDurationTicks;
58
+ }
59
+ /** True when the file stores per-record onsets, i.e. when probing can learn anything. */
60
+ function hasTimekeeping(header) {
61
+ return header.annotationSignalIndices.length > 0;
62
+ }
63
+ async function probeOnset(source, header, recordIndex, options) {
64
+ const records = { start: recordIndex, count: 1 };
65
+ const bytes = await readRecordBytes(source, header, records, options);
66
+ const decoded = decodeAnnotations(header, bytes, records, options);
67
+ // decodeAnnotations fills one entry per record in the range, always; the range is one record.
68
+ const ticks = decoded.recordOnsetTicks[0] ?? nominalOnsetTicks(header, recordIndex);
69
+ return { ticks, diagnostics: decoded.diagnostics };
70
+ }
71
+ /**
72
+ * The one `EdfRecordIndex` implementation.
73
+ *
74
+ * `locate` is written against `onsetOf` alone, so the probed index and the complete one share it:
75
+ * the first pays one read per probe and memoises, the second answers from an array it already
76
+ * has. The search is the same either way, and so is its behaviour at a gap.
77
+ */
78
+ function createIndex(input) {
79
+ const { recordCount, recordDurationTicks, startOffsetTicks, onsetOf } = input;
80
+ function assertRecordIndex(recordIndex) {
81
+ if (Number.isSafeInteger(recordIndex) && recordIndex >= 0 && recordIndex < recordCount) {
82
+ return;
83
+ }
84
+ throw new EdfRangeError(`record ${recordIndex} is not one of the ${recordCount} data records this file contains, ` +
85
+ 'so it has no onset to read. Next: pass an index in ' +
86
+ `0..${recordCount - 1}, or call locate(seconds) to find one for a time.`, { requested: { start: recordIndex, count: 1 }, available: { start: 0, count: recordCount } });
87
+ }
88
+ async function onsetTicks(recordIndex, options) {
89
+ assertRecordIndex(recordIndex);
90
+ return onsetOf(recordIndex, options);
91
+ }
92
+ /**
93
+ * The last record whose onset is at or before `targetTicks`, or `undefined` when every record
94
+ * starts after it.
95
+ *
96
+ * Binary search over a monotonic sequence, and monotonicity is verified at every pair the
97
+ * search actually observes: a violation makes every time-based answer for the file wrong, so
98
+ * `assertMonotonicOnsets` throws rather than letting the search return a plausible record.
99
+ */
100
+ async function findRecordAtOrBefore(targetTicks, options) {
101
+ let low = 0;
102
+ let lowTicks = await onsetOf(0, options);
103
+ if (targetTicks < lowTicks)
104
+ return undefined;
105
+ let high = recordCount - 1;
106
+ if (high === low)
107
+ return { recordIndex: low, onsetTicks: lowTicks };
108
+ let highTicks = await onsetOf(high, options);
109
+ assertMonotonicOnsets({ recordIndex: low, onsetTicks: lowTicks }, { recordIndex: high, onsetTicks: highTicks });
110
+ if (targetTicks >= highTicks)
111
+ return { recordIndex: high, onsetTicks: highTicks };
112
+ // Invariant: lowTicks <= target < highTicks, and low < high.
113
+ while (high - low > 1) {
114
+ const middle = low + Math.floor((high - low) / 2);
115
+ const middleTicks = await onsetOf(middle, options);
116
+ assertMonotonicOnsets({ recordIndex: low, onsetTicks: lowTicks }, { recordIndex: middle, onsetTicks: middleTicks });
117
+ assertMonotonicOnsets({ recordIndex: middle, onsetTicks: middleTicks }, { recordIndex: high, onsetTicks: highTicks });
118
+ if (middleTicks <= targetTicks) {
119
+ low = middle;
120
+ lowTicks = middleTicks;
121
+ }
122
+ else {
123
+ high = middle;
124
+ highTicks = middleTicks;
125
+ }
126
+ }
127
+ return { recordIndex: low, onsetTicks: lowTicks };
128
+ }
129
+ async function locate(seconds, options) {
130
+ if (recordCount <= 0)
131
+ return undefined;
132
+ // `seconds` is elapsed recording time; stored onsets are relative to the header start time.
133
+ const targetTicks = secondsToTicks(seconds) + startOffsetTicks;
134
+ const found = await findRecordAtOrBefore(targetTicks, options);
135
+ if (found === undefined)
136
+ return undefined;
137
+ const recordStartSeconds = ticksToSeconds(found.onsetTicks - startOffsetTicks);
138
+ if (recordDurationTicks === 0n) {
139
+ // Zero-duration records occupy no time, so only the instant itself is inside one. The last
140
+ // record sharing that instant is the one returned, which is what the search converges on.
141
+ if (targetTicks !== found.onsetTicks)
142
+ return undefined;
143
+ return {
144
+ recordIndex: found.recordIndex,
145
+ recordStartSeconds,
146
+ offsetInRecordSeconds: 0,
147
+ };
148
+ }
149
+ // Past the end of the record it follows: the time is in a gap, or after the recording.
150
+ if (targetTicks >= found.onsetTicks + recordDurationTicks)
151
+ return undefined;
152
+ return {
153
+ recordIndex: found.recordIndex,
154
+ recordStartSeconds,
155
+ offsetInRecordSeconds: ticksToSeconds(targetTicks - found.onsetTicks),
156
+ };
157
+ }
158
+ return {
159
+ coverage: input.coverage,
160
+ recordCount,
161
+ segments: input.segments,
162
+ gaps: input.gaps,
163
+ onsetTicks,
164
+ locate,
165
+ };
166
+ }
167
+ /**
168
+ * The timeline and a lazily probing index, for two reads at most.
169
+ *
170
+ * The probes are records 0 and `recordCount - 1` (one probe for a single-record file, none at all
171
+ * when the file has no annotation signal). Both are memoised into the index, so `onsetTicks(0)`
172
+ * and `onsetTicks(recordCount - 1)` are free after `openEdf`.
173
+ *
174
+ * `index.coverage` stays `'probed'` and `index.segments`/`index.gaps` stay `undefined` until
175
+ * `buildRecordIndex()` promotes them. Nothing on the returned object can be mistaken for a
176
+ * verified statement that the recording is continuous.
177
+ */
178
+ export async function buildTimeline(source, header, options) {
179
+ const recordCount = header.recordCount;
180
+ const timekept = hasTimekeeping(header);
181
+ const strict = options?.strict === true;
182
+ const probeOptions = (readOptions) => readOptions === undefined ? { strict } : { ...readOptions, strict };
183
+ const memo = new Map();
184
+ const probes = [];
185
+ const probeDiagnostics = [];
186
+ const probeIndices = recordCount === 0 ? [] : recordCount === 1 ? [0] : [0, recordCount - 1];
187
+ for (const recordIndex of probeIndices) {
188
+ if (!timekept) {
189
+ const ticks = nominalOnsetTicks(header, recordIndex);
190
+ memo.set(recordIndex, ticks);
191
+ probes.push({ recordIndex, onsetTicks: ticks });
192
+ continue;
193
+ }
194
+ const probe = await probeOnset(source, header, recordIndex, probeOptions(options));
195
+ memo.set(recordIndex, probe.ticks);
196
+ probeDiagnostics.push(...probe.diagnostics);
197
+ probes.push({ recordIndex, onsetTicks: probe.ticks });
198
+ }
199
+ const timeline = buildTimelineFromProbes({ header, probes, probeDiagnostics }, options);
200
+ async function onsetOf(recordIndex, readOptions) {
201
+ const cached = memo.get(recordIndex);
202
+ if (cached !== undefined)
203
+ return cached;
204
+ if (!timekept) {
205
+ const ticks = nominalOnsetTicks(header, recordIndex);
206
+ memo.set(recordIndex, ticks);
207
+ return ticks;
208
+ }
209
+ const probe = await probeOnset(source, header, recordIndex, probeOptions(readOptions));
210
+ memo.set(recordIndex, probe.ticks);
211
+ return probe.ticks;
212
+ }
213
+ const index = createIndex({
214
+ coverage: 'probed',
215
+ recordCount,
216
+ recordDurationTicks: header.recordDurationTicks,
217
+ startOffsetTicks: timeline.startOffsetTicks,
218
+ segments: undefined,
219
+ gaps: undefined,
220
+ onsetOf,
221
+ });
222
+ return { timeline, index };
223
+ }
224
+ /**
225
+ * Every record's onset, read in bounded chunks.
226
+ *
227
+ * `onProgress` is called once per chunk with the number of records finished, so a caller can show
228
+ * a bar for the one operation in edfcore whose cost is proportional to the file.
229
+ *
230
+ * A file with no annotation signal is not scanned: its record onsets are arithmetic, so reading
231
+ * the data would answer a question the bytes do not contain. `onProgress` is still called once,
232
+ * with the traversal complete, so a caller's bar finishes.
233
+ */
234
+ async function scanOnsets(recording, options) {
235
+ const { source, header } = recording;
236
+ const recordCount = header.recordCount;
237
+ const onsets = new BigInt64Array(recordCount);
238
+ if (!hasTimekeeping(header)) {
239
+ for (let recordIndex = 0; recordIndex < recordCount; recordIndex += 1) {
240
+ onsets[recordIndex] = nominalOnsetTicks(header, recordIndex);
241
+ }
242
+ options?.onProgress?.(recordCount, recordCount);
243
+ return onsets;
244
+ }
245
+ const chunkRecords = scanChunkRecords(header, options?.maxMaterializeBytes);
246
+ let scanned = 0;
247
+ while (scanned < recordCount) {
248
+ const records = {
249
+ start: scanned,
250
+ count: Math.min(chunkRecords, recordCount - scanned),
251
+ };
252
+ const bytes = await readRecordBytes(source, header, records, options);
253
+ const decoded = decodeAnnotations(header, bytes, records, options);
254
+ onsets.set(decoded.recordOnsetTicks, scanned);
255
+ scanned += records.count;
256
+ options?.onProgress?.(scanned, recordCount);
257
+ }
258
+ return onsets;
259
+ }
260
+ /**
261
+ * A `'complete'` index: every onset verified, with the segments and gaps they imply.
262
+ *
263
+ * This is one of only two functions that read the whole file, the other being `validateRecording`, and it is never called
264
+ * implicitly. Its diagnostics are deliberately not returned — an `EdfRecordIndex` is a
265
+ * structural answer, and `validateRecording()` is the call that reports on a traversal — but a
266
+ * non-monotonic timeline still throws, because no index over it would mean anything.
267
+ *
268
+ * `EdfRecording` is a plain struct, so the returned index is used by rebuilding one:
269
+ * `readWindow({ ...recording, index }, selection)`.
270
+ */
271
+ export async function buildRecordIndex(recording, options) {
272
+ const { header, timeline } = recording;
273
+ const onsets = await scanOnsets(recording, options);
274
+ assertMonotonicOnsetArray(onsets);
275
+ const segmentation = buildSegmentation(onsets, header.recordDurationTicks, timeline.startOffsetTicks);
276
+ async function onsetOf(recordIndex) {
277
+ // Bounds were checked by `createIndex` before this is reached.
278
+ return onsets[recordIndex] ?? nominalOnsetTicks(header, recordIndex);
279
+ }
280
+ return createIndex({
281
+ coverage: 'complete',
282
+ recordCount: header.recordCount,
283
+ recordDurationTicks: header.recordDurationTicks,
284
+ startOffsetTicks: timeline.startOffsetTicks,
285
+ segments: segmentation.segments,
286
+ gaps: segmentation.gaps,
287
+ onsetOf,
288
+ });
289
+ }
290
+ //# sourceMappingURL=record-index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record-index.js","sourceRoot":"","sources":["../src/record-index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * The convenience layer: composition over the primitives, and no behaviour of its own.
3
+ *
4
+ * Layer 6. Everything here is a few lines of arrangement around `io/read.ts`, `decode/` and
5
+ * `time/`. That is the point — a facade that made its own decisions would be a second place where
6
+ * the library's rules live, and the rules are what edfcore is.
7
+ *
8
+ * Three shapes are load-bearing:
9
+ *
10
+ * - `readRecords` returns exactly ONE chunk and costs exactly one read. The caller named the
11
+ * records, so a gap inside them cannot surprise anyone.
12
+ * - `readWindow` ALWAYS returns an array, one chunk per contiguous run, including for a
13
+ * continuous file where the array always has one element. If two shapes existed, consumers
14
+ * would write against the easy one and misbehave on EDF+D. A window entirely inside a gap
15
+ * returns `[]`, and nothing is ever filled in: there is no gap-fill and no gap-fill option.
16
+ * - Chunks stay RECORD-ALIGNED and are therefore usually wider than the window asked for. The
17
+ * exact per-signal narrowing is `trimToWindow`, which is pure and testable without I/O.
18
+ *
19
+ * Every chunk carries the onsets of the records it contains, verified from the bytes that were
20
+ * already read — annotation regions live inside those bytes, so this costs no extra I/O and makes
21
+ * a sparsely indexed file safe for the data you actually received.
22
+ */
23
+ import type { ByteSource, DecodeAnnotationsOptions, EdfAnnotationsResult, EdfChunk, EdfRecording, OpenOptions, ReadOptions, RecordRange, RecordSelection, WindowSelection } from './types.js';
24
+ /**
25
+ * Open a recording: the header, then the timeline.
26
+ *
27
+ * Never scans the file. On a plain EDF or BDF this is two reads in total; on an EDF+ or BDF+ file
28
+ * it is two more, probing the first and last records for their timekeeping onsets.
29
+ */
30
+ export declare function openEdf(source: ByteSource, options?: OpenOptions): Promise<EdfRecording>;
31
+ /** Exactly one chunk and exactly one read: you named the records, so gaps cannot surprise you. */
32
+ export declare function readRecords(recording: EdfRecording, selection: RecordSelection, options?: ReadOptions): Promise<EdfChunk>;
33
+ /**
34
+ * A time window, as one chunk per contiguous run of records.
35
+ *
36
+ * Always an array. `[]` means the window is entirely inside a gap or entirely outside the
37
+ * recording — never that the read failed. Chunks are record-aligned and may be wider than asked
38
+ * for; `trimToWindow(header, chunkSignal, startSeconds, durationSeconds)` narrows them exactly.
39
+ *
40
+ * Runs are read one after another rather than concurrently, so the read pattern a caller observes
41
+ * is the one this function issued, in order, with no burst it did not ask for. Concurrency over a
42
+ * `ByteSource` belongs to the source — `httpSource` has `maxConcurrency` — not here.
43
+ *
44
+ * On a discontinuous file a probed index cannot map seconds to records; `resolveTimeWindow`
45
+ * refuses rather than guessing. Build a complete index and rebuild the recording around it:
46
+ * `const index = await buildRecordIndex(rec); await readWindow({ ...rec, index }, selection)`.
47
+ */
48
+ export declare function readWindow(recording: EdfRecording, selection: WindowSelection, options?: ReadOptions): Promise<readonly EdfChunk[]>;
49
+ /**
50
+ * The annotations in a record range, in one read.
51
+ *
52
+ * `records` is required and has no default. A full-file annotation scan is a legitimate thing to
53
+ * want and an expensive thing to do by accident, so it is always visible in the caller's source
54
+ * as `{ start: 0, count: recording.header.recordCount }`.
55
+ */
56
+ export declare function readAnnotations(recording: EdfRecording, records: RecordRange, options?: DecodeAnnotationsOptions & ReadOptions): Promise<EdfAnnotationsResult>;
57
+ //# sourceMappingURL=recording.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recording.d.ts","sourceRoot":"","sources":["../src/recording.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAUH,OAAO,KAAK,EACV,UAAU,EACV,wBAAwB,EACxB,oBAAoB,EACpB,QAAQ,EAKR,YAAY,EAEZ,WAAW,EACX,WAAW,EACX,WAAW,EACX,eAAe,EACf,eAAe,EAChB,MAAM,YAAY,CAAC;AAEpB;;;;;GAKG;AACH,wBAAsB,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAI9F;AAqHD,kGAAkG;AAClG,wBAAsB,WAAW,CAC/B,SAAS,EAAE,YAAY,EACvB,SAAS,EAAE,eAAe,EAC1B,OAAO,CAAC,EAAE,WAAW,GACpB,OAAO,CAAC,QAAQ,CAAC,CAEnB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,UAAU,CAC9B,SAAS,EAAE,YAAY,EACvB,SAAS,EAAE,eAAe,EAC1B,OAAO,CAAC,EAAE,WAAW,GACpB,OAAO,CAAC,SAAS,QAAQ,EAAE,CAAC,CAa9B;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,SAAS,EAAE,YAAY,EACvB,OAAO,EAAE,WAAW,EACpB,OAAO,CAAC,EAAE,wBAAwB,GAAG,WAAW,GAC/C,OAAO,CAAC,oBAAoB,CAAC,CAG/B"}
@@ -0,0 +1,178 @@
1
+ /**
2
+ * The convenience layer: composition over the primitives, and no behaviour of its own.
3
+ *
4
+ * Layer 6. Everything here is a few lines of arrangement around `io/read.ts`, `decode/` and
5
+ * `time/`. That is the point — a facade that made its own decisions would be a second place where
6
+ * the library's rules live, and the rules are what edfcore is.
7
+ *
8
+ * Three shapes are load-bearing:
9
+ *
10
+ * - `readRecords` returns exactly ONE chunk and costs exactly one read. The caller named the
11
+ * records, so a gap inside them cannot surprise anyone.
12
+ * - `readWindow` ALWAYS returns an array, one chunk per contiguous run, including for a
13
+ * continuous file where the array always has one element. If two shapes existed, consumers
14
+ * would write against the easy one and misbehave on EDF+D. A window entirely inside a gap
15
+ * returns `[]`, and nothing is ever filled in: there is no gap-fill and no gap-fill option.
16
+ * - Chunks stay RECORD-ALIGNED and are therefore usually wider than the window asked for. The
17
+ * exact per-signal narrowing is `trimToWindow`, which is pure and testable without I/O.
18
+ *
19
+ * Every chunk carries the onsets of the records it contains, verified from the bytes that were
20
+ * already read — annotation regions live inside those bytes, so this costs no extra I/O and makes
21
+ * a sparsely indexed file safe for the data you actually received.
22
+ */
23
+ import { decodeDigitalCounted } from './decode/digital.js';
24
+ import { EdfChannelNotFoundError } from './errors.js';
25
+ import { readHeader, readRecordBytes } from './io/read.js';
26
+ import { buildTimeline } from './record-index.js';
27
+ import { decodeAnnotations } from './tal/annotations.js';
28
+ import { ticksToSeconds } from './tal/ticks.js';
29
+ import { assertMonotonicOnsetArray } from './time/timeline.js';
30
+ import { resolveTimeWindow } from './time/window.js';
31
+ /**
32
+ * Open a recording: the header, then the timeline.
33
+ *
34
+ * Never scans the file. On a plain EDF or BDF this is two reads in total; on an EDF+ or BDF+ file
35
+ * it is two more, probing the first and last records for their timekeeping onsets.
36
+ */
37
+ export async function openEdf(source, options) {
38
+ const header = await readHeader(source, options);
39
+ const { timeline, index } = await buildTimeline(source, header, options);
40
+ return { source, header, timeline, index };
41
+ }
42
+ /**
43
+ * The signals to decode, deduplicated and in the order given.
44
+ *
45
+ * An annotation signal is refused with a plain `RangeError` rather than an `EdfError`: its bytes
46
+ * are TAL text, and decoding them as samples produces numbers that look exactly like a signal.
47
+ * That is the failure this library exists to prevent, and it can only ever be a caller's mistake.
48
+ */
49
+ function resolveSignals(header, signalIndices) {
50
+ const seen = new Set();
51
+ const signals = [];
52
+ for (const signalIndex of signalIndices) {
53
+ if (seen.has(signalIndex))
54
+ continue;
55
+ seen.add(signalIndex);
56
+ const signal = header.signals[signalIndex];
57
+ if (signal === undefined) {
58
+ throw new EdfChannelNotFoundError(`signalIndex ${signalIndex} is outside the ${header.signals.length} signals this file ` +
59
+ 'declares. Next: pass an index from header.dataSignalIndices, or resolve one with ' +
60
+ 'getSignal(header, label).', { selector: signalIndex, availableLabels: header.signals.map((s) => s.label) });
61
+ }
62
+ if (signal.kind === 'annotations') {
63
+ throw new RangeError(`signal ${signalIndex} (${JSON.stringify(signal.label)}) is this file's annotations ` +
64
+ 'channel: its bytes are TAL text, not samples, so decoding them as samples would ' +
65
+ 'produce numbers that look like a signal. Next: call readAnnotations(recording, ' +
66
+ 'records) for it, and pass only header.dataSignalIndices here.');
67
+ }
68
+ signals.push(signal);
69
+ }
70
+ return signals;
71
+ }
72
+ /**
73
+ * The gap immediately before `recordStart`, when the index knows where the gaps are.
74
+ *
75
+ * `undefined` for a probed index, and that is not a claim that there is no gap — it is the honest
76
+ * answer that nobody has read the onsets in between. `buildRecordIndex()` is what turns the
77
+ * question into an answerable one.
78
+ */
79
+ function gapBefore(index, recordStart) {
80
+ const { segments, gaps } = index;
81
+ if (segments === undefined || gaps === undefined)
82
+ return undefined;
83
+ for (const gap of gaps) {
84
+ const after = segments[gap.afterSegmentIndex];
85
+ if (after !== undefined && after.records.start === recordStart)
86
+ return gap;
87
+ }
88
+ return undefined;
89
+ }
90
+ /**
91
+ * One record range in, one chunk out, one read.
92
+ *
93
+ * The annotation regions of these records are already inside the bytes that were read, so their
94
+ * onsets are decoded and checked for free. That is what makes `chunk.startSeconds` the record's
95
+ * TRUE start on an EDF+D file rather than a nominal `start * recordDuration` that a gap would
96
+ * have invalidated.
97
+ */
98
+ async function readChunk(recording, records, signalIndices, options) {
99
+ const { source, header, timeline, index } = recording;
100
+ const signals = resolveSignals(header, signalIndices);
101
+ const bytes = await readRecordBytes(source, header, records, options);
102
+ const byteOffset = header.headerByteLength + records.start * header.recordByteLength;
103
+ // Never strict, and not because the flag was lost: a read that threw on an impolite TAL in a
104
+ // record the caller asked for would return no samples at all over a defect in a different
105
+ // channel. The defects land on `chunk.diagnostics`, next to the data they were found beside.
106
+ const annotations = decodeAnnotations(header, bytes, records);
107
+ const onsets = annotations.recordOnsetTicks;
108
+ assertMonotonicOnsetArray(onsets, records.start);
109
+ const durationTicks = header.recordDurationTicks;
110
+ const nominalFirstTicks = timeline.startOffsetTicks + BigInt(records.start) * durationTicks;
111
+ const firstOnsetTicks = onsets[0] ?? nominalFirstTicks;
112
+ const lastOnsetTicks = records.count > 0 ? (onsets[records.count - 1] ?? firstOnsetTicks) : firstOnsetTicks;
113
+ const startTicks = firstOnsetTicks - timeline.startOffsetTicks;
114
+ // The SPAN of the chunk, not the time it covers: they are equal for one contiguous run, which
115
+ // is what readWindow produces, and they differ when a caller names records across a gap.
116
+ const spanTicks = records.count > 0 ? lastOnsetTicks + durationTicks - firstOnsetTicks : 0n;
117
+ const startSeconds = ticksToSeconds(startTicks);
118
+ const chunkSignals = signals.map((signal) => {
119
+ const decoded = decodeDigitalCounted(header, bytes, records, signal.index, undefined, options);
120
+ return {
121
+ signalIndex: signal.index,
122
+ sampleCount: decoded.digital.length,
123
+ digital: decoded.digital,
124
+ firstSampleIndex: records.start * signal.samplesPerRecord,
125
+ startSeconds,
126
+ outOfDigitalRangeCount: decoded.outOfDigitalRangeCount,
127
+ };
128
+ });
129
+ return {
130
+ records,
131
+ startSeconds,
132
+ durationSeconds: ticksToSeconds(spanTicks),
133
+ byteOffset,
134
+ byteLength: bytes.length,
135
+ signals: Object.freeze(chunkSignals),
136
+ precededByGap: gapBefore(index, records.start),
137
+ diagnostics: annotations.diagnostics,
138
+ };
139
+ }
140
+ /** Exactly one chunk and exactly one read: you named the records, so gaps cannot surprise you. */
141
+ export async function readRecords(recording, selection, options) {
142
+ return readChunk(recording, selection.records, selection.signalIndices, options);
143
+ }
144
+ /**
145
+ * A time window, as one chunk per contiguous run of records.
146
+ *
147
+ * Always an array. `[]` means the window is entirely inside a gap or entirely outside the
148
+ * recording — never that the read failed. Chunks are record-aligned and may be wider than asked
149
+ * for; `trimToWindow(header, chunkSignal, startSeconds, durationSeconds)` narrows them exactly.
150
+ *
151
+ * Runs are read one after another rather than concurrently, so the read pattern a caller observes
152
+ * is the one this function issued, in order, with no burst it did not ask for. Concurrency over a
153
+ * `ByteSource` belongs to the source — `httpSource` has `maxConcurrency` — not here.
154
+ *
155
+ * On a discontinuous file a probed index cannot map seconds to records; `resolveTimeWindow`
156
+ * refuses rather than guessing. Build a complete index and rebuild the recording around it:
157
+ * `const index = await buildRecordIndex(rec); await readWindow({ ...rec, index }, selection)`.
158
+ */
159
+ export async function readWindow(recording, selection, options) {
160
+ const ranges = resolveTimeWindow(recording.timeline, recording.index, selection.startSeconds, selection.durationSeconds);
161
+ const chunks = [];
162
+ for (const records of ranges) {
163
+ chunks.push(await readChunk(recording, records, selection.signalIndices, options));
164
+ }
165
+ return Object.freeze(chunks);
166
+ }
167
+ /**
168
+ * The annotations in a record range, in one read.
169
+ *
170
+ * `records` is required and has no default. A full-file annotation scan is a legitimate thing to
171
+ * want and an expensive thing to do by accident, so it is always visible in the caller's source
172
+ * as `{ start: 0, count: recording.header.recordCount }`.
173
+ */
174
+ export async function readAnnotations(recording, records, options) {
175
+ const bytes = await readRecordBytes(recording.source, recording.header, records, options);
176
+ return decodeAnnotations(recording.header, bytes, records, options);
177
+ }
178
+ //# sourceMappingURL=recording.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recording.js","sourceRoot":"","sources":["../src/recording.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * `decodeAnnotations`: TALs plus EDF+ semantics.
3
+ *
4
+ * Layer 3. Sole owner of timekeeping extraction and of the annotation sort. The byte grammar
5
+ * lives in `grammar.ts`; everything here is about which TAL means what.
6
+ *
7
+ * Three rules the rest of the library depends on:
8
+ *
9
+ * 1. The FIRST TAL of the FIRST annotation signal of the file is that record's timekeeping TAL.
10
+ * "First" is a position, not "the first one that parsed", and "first annotation signal" is
11
+ * `header.annotationSignalIndices[0]` — not the first signal this call was asked for. An
12
+ * additional annotation signal carries NO timekeeping TAL, and stripping its first TAL would
13
+ * silently delete a real event.
14
+ * 2. `recordOnsetTicks` has one entry for EVERY record in the decoded range, always. A record
15
+ * whose timekeeping TAL is missing gets the derived onset `start + recordIndex * duration`
16
+ * rather than a hole or a sentinel, and `TIMEKEEPING_TAL_MISSING` carries the record index so
17
+ * the derivation is never invisible. Every timeline in edfcore is built from this array.
18
+ * 3. Onsets are exposed under both conventions as separately named fields, never as an option:
19
+ * `onsetSecondsFromHeaderStart` is the verbatim on-disk value and
20
+ * `onsetSecondsFromFirstRecord` is rebased to record 0's true start. `onsetTicks` is exact
21
+ * and is the only one worth comparing.
22
+ *
23
+ * Diagnostic volume is bounded on purpose, by one test: does another occurrence of this code
24
+ * carry information available nowhere else? `TIMEKEEPING_TAL_MISSING` does — it names a record
25
+ * whose onset was derived — so it is reported per record. `NEGATIVE_ANNOTATION_ONSET` and
26
+ * `TIMEKEEPING_TAL_NONCONFORMANT` do not: the onsets are in the result and the shorthand is a
27
+ * property of the writer, so they are reported once per call. The grammar's own defects are
28
+ * deduplicated per region and carry an occurrence count.
29
+ *
30
+ * Record-onset spacing and monotonicity are deliberately NOT checked here. This module produces
31
+ * `recordOnsetTicks`; `time/timeline.ts` owns enforcing what a valid timeline is, and splitting
32
+ * that would give one rule two owners.
33
+ */
34
+ import type { DecodeAnnotationsOptions, EdfAnnotationsResult, EdfHeader, RecordRange } from '../types.js';
35
+ export declare function decodeAnnotations(header: EdfHeader, recordBytes: Uint8Array, records: RecordRange, options?: DecodeAnnotationsOptions): EdfAnnotationsResult;
36
+ //# sourceMappingURL=annotations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"annotations.d.ts","sourceRoot":"","sources":["../../src/tal/annotations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAMH,OAAO,KAAK,EACV,wBAAwB,EAExB,oBAAoB,EACpB,SAAS,EAET,WAAW,EACZ,MAAM,aAAa,CAAC;AAmTrB,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,UAAU,EACvB,OAAO,EAAE,WAAW,EACpB,OAAO,CAAC,EAAE,wBAAwB,GACjC,oBAAoB,CAsItB"}