/** * The record index: two probes at open, one targeted probe on demand, a full scan only if asked. * * Layer 6. This module owns the I/O STRATEGY for record onsets and nothing else — what a valid * timeline is belongs to `time/timeline.ts`, what a timekeeping TAL is belongs to * `tal/annotations.ts`, and segmentation belongs to `time/segments.ts`. Every onset that reaches * those modules from here came out of `decodeAnnotations`, so the "first TAL of the first * annotation signal" rule has exactly one implementation. * * Cost is the design constraint. Opening a million-record EDF+D over HTTP must not read the file, * so: * * - a file with no annotation signal is probed ZERO times: without a timekeeping TAL there is no * per-record onset on disk, and record `r` starts at `r * recordDuration` by definition; * - otherwise `buildTimeline` probes exactly two records, the first and the last, which detects * any NET drift of the timeline for two reads. It is not a proof of contiguity, and * `time/timeline.ts` says so in the diagnostic it emits; * - `onsetTicks(r)` reads that ONE record and memoises the answer, so `locate()` costs * O(log recordCount) reads and a second `locate()` nearby costs almost none; * - `buildRecordIndex()` is the only function here that touches every record, it is chunked so * memory stays bounded whatever the file size, and it is never called implicitly. * * A probe reads a whole data record rather than just the annotation signal's region. That is the * design's "unit of I/O" decision — the unit is the record range, never the channel range — and * it is also what lets `decodeAnnotations` own the timekeeping rule: it requires the record's * full bytes, and reading less would mean reimplementing that rule here. */ import type { BuildIndexOptions, ByteSource, EdfGap, EdfHeader, EdfRecordIndex, EdfRecording, EdfSegment, EdfTimeline, OpenOptions } from './types.js'; /** Records per chunk of a full traversal: bounded memory, and never fewer than one record. */ export declare function scanChunkRecords(header: EdfHeader, maxMaterializeBytes?: number): number; /** * The timeline and a lazily probing index, for two reads at most. * * The probes are records 0 and `recordCount - 1` (one probe for a single-record file, none at all * when the file has no annotation signal). Both are memoised into the index, so `onsetTicks(0)` * and `onsetTicks(recordCount - 1)` are free after `openEdf`. * * `index.coverage` stays `'probed'` and `index.segments`/`index.gaps` stay `undefined` until * `buildRecordIndex()` promotes them. Nothing on the returned object can be mistaken for a * verified statement that the recording is continuous. */ export declare function buildTimeline(source: ByteSource, header: EdfHeader, options?: OpenOptions): Promise<{ timeline: EdfTimeline; index: EdfRecordIndex; }>; /** * A `'complete'` index: every onset verified, with the segments and gaps they imply. * * This is one of only two functions that read the whole file, the other being * `validateRecording`, and it is never called implicitly. Its diagnostics are deliberately not * returned — an `EdfRecordIndex` is a structural answer, and `validateRecording()` is the call * that reports on a traversal — but a non-monotonic timeline still throws, because no index over * it would mean anything. * * `EdfRecording` is a plain struct, so the returned index is used by rebuilding one: * `readWindow({ ...recording, index }, selection)`. */ export declare function buildRecordIndex(recording: EdfRecording, options?: BuildIndexOptions): Promise; /** * Whether the records run without gaps — or whether nobody has checked. * * Three answers, not two. A probed index has read record 0 and the last record and nothing in * between, so it cannot rule out a gap in the middle; `'unknown'` is the truthful answer there, * and collapsing it into `false` would report a discontinuity nobody observed, while collapsing * it into `true` would claim a contiguity nobody verified. * * `buildRecordIndex()` is what turns `'unknown'` into a real answer. */ export declare function contiguityOf(index: EdfRecordIndex): 'contiguous' | 'discontinuous' | 'unknown'; /** * The segment covering an instant, or `undefined` when the instant falls in a gap or outside the * recording. * * Pure and synchronous, which is the point: `index.locate()` answers the same question by probing * the file, and a viewer that asks on every mouse move should not be issuing reads. A completed * index already holds the segments, so this is a binary search over them. * * THROWS on a probed index rather than returning `undefined`. `undefined` here means "no records * cover this time", and a probed index has read record 0 and the last record and nothing between — * it does not know where the segments are, so it cannot say that about any instant in the middle. * Returning `undefined` would merge "there is a gap here" with "nobody looked", which are the two * answers a caller most needs to keep apart. * * `seconds` is on the recording's own axis: `t = 0` is the start of record 0, matching * `segment.startSeconds`, `readWindow` and `readEnvelope`. * * ON A ZERO RECORD DURATION this returns `undefined` for every time, and that is correct rather * than a gap in the implementation. Records then occupy no time at all, so each segment's * half-open interval `[start, start)` is empty and no instant is inside one. A real sleep-staging * file is shaped exactly like that — legal EDF, and the same reason `sampleAt` refuses such a file * outright. Index by record with `readRecords` instead. */ export declare function segmentAt(index: EdfRecordIndex, seconds: number): EdfSegment | undefined; /** * The gap covering an instant, or `undefined` when a record covers it. * * The complement of `segmentAt`, and the reason it exists separately: `segmentAt` returning * `undefined` tells a viewer there is no data under the cursor and nothing else. What a viewer * then wants — how long the hole is, and when the recording resumes — is on the `EdfGap`. * * Exactly one of the two returns a value for any instant strictly inside the recording, and * neither does for a time before the first record or after the last. Refuses a probed index and a * non-finite time for the same reasons `segmentAt` does. */ export declare function gapAt(index: EdfRecordIndex, seconds: number): EdfGap | undefined; //# sourceMappingURL=record-index.d.ts.map