/** * Min/max envelope decimation. * * Layer 7. A twelve-hour recording at 256 Hz is eleven million samples per channel, and a plot is * a thousand pixels wide. Something has to reduce eleven million numbers to a thousand, and which * reduction you pick decides whether the picture is true. * * Taking every 11,000th sample is the obvious choice and the wrong one: a spike, a spindle or an * artifact is a handful of samples wide, so subsampling hits it with probability near zero and * the trace looks calm exactly where a reader most needs it not to. Keeping the MINIMUM and * MAXIMUM of each bucket keeps every extreme, at two numbers per pixel. That is the reduction a * waveform viewer wants, and it is why this exists as its own function rather than as an option * on `readWindow`: the return type is different, so an option would have to change it. * * This is decimation for display, not analysis. There is no filtering and no anti-aliasing — * an envelope is a faithful summary of the samples that are there, not a resampled signal, and * resampling remains a permanent non-goal. * * Memory is bounded by the record chunk, never by the window: a run of a million records is * folded into the buckets a chunk at a time, so an envelope over a whole recording costs the * buckets plus one chunk. */ import type { EdfChunkSignal, EdfEnvelopeChunk, EdfEnvelopeSignal, EdfPhysicalEnvelope, EdfRecording, EdfSignal, EnvelopeSelection, ReadOptions } from './types.js'; /** * Reduces a time window to per-bucket minima and maxima, one chunk per contiguous run. * * The shape mirrors `readWindow` exactly — an array of chunks, one per run, empty when the window * selects nothing — so a caller that already handles gaps handles envelopes for free. */ export declare function readEnvelope(recording: EdfRecording, selection: EnvelopeSelection, options?: ReadOptions): Promise; /** * Converts a digital envelope to physical units. * * Not `toPhysical` applied twice, and the reason is the sign of the gain. The affine transform * `bitValue * (offset + digital)` is DECREASING when `bitValue` is negative — a spec-sanctioned * arrangement that edfcore reports rather than rejects — and a decreasing map sends the smallest * digital value to the largest physical one. Mapping `min` to `min` would then produce an * envelope whose lower bound is above its upper bound, and a viewer would draw it inside out. */ export declare function toPhysicalEnvelope(signal: EdfSignal, envelope: EdfEnvelopeSignal, out?: EdfPhysicalEnvelope): EdfPhysicalEnvelope; /** * The envelope of an already-decoded chunk signal, without another read. * * For a caller who has samples in hand and wants them plotted: same reduction, same bucket rule, * no I/O. * * `sampleCount` bounds the reduction, not `digital.length`. `EdfChunkSignal` documents * `sampleCount` as the truth and every producer inside edfcore makes the two equal — `decodeDigital` * narrows an oversized reused buffer with `subarray` before it escapes, so no read path can hand * this a padded array. The bound is here because a CALLER can build an `EdfChunkSignal`, and * because `mergeChunks` and `trimToWindow` already take `sampleCount` as authoritative: two * helpers defending and one not is the worst of the three states, whichever way the contract is * eventually written down. */ export declare function envelopeOfSamples(chunkSignal: EdfChunkSignal, buckets: number): EdfEnvelopeSignal; /** * The envelope of a window, at a chosen time resolution rather than a chosen bucket count. * * `readEnvelope` takes buckets because a plot has a pixel width. This takes seconds per bucket, * which is what a fixed-scale view wants — 30 s per bucket for a sleep hypnogram, whatever the * window length. Deriving one from the other by hand means dividing and rounding, and rounding * the wrong way produces a final bucket covering a sliver of time that reads as a dropout. * * The bucket count is computed PER RUN, from that run's own span, not once from the window. A * chunk covers one record-aligned contiguous run, and a run is not the window: an EDF+D window * spanning a gap produces two runs of different lengths, and even a contiguous window that does * not start on a record boundary produces a run wider than it asked for. Handing one bucket count * to every chunk therefore delivered a different resolution in each — a window of 11 s asked at * 1 s per bucket came back as 0.27 s per bucket in one chunk and 0.09 s in the other, which are * not commensurable, so a viewer cannot place the two on one axis. That is the whole promise of * this function, so it is computed where the run's length is known (fixed in 0.2.31). * * The bucket count was only half of it. A bucket is a fixed WIDTH IN TIME here, and until 0.3.9 * the fold still divided each run evenly into its own count — so the width followed the run * exactly as before whenever the span was not a whole multiple of the request. A 100 s run at 30 s * per bucket got four buckets of 25 s, while a 60 s run in the same call got two of 30 s. The * bucket a sample lands in is now decided by WHEN it is, so the last bucket of a run is short by * whatever the division left over — the sliver this documentation always described — and every * chunk of every call reports the width that was asked for. */ export declare function readEnvelopeAtResolution(recording: EdfRecording, selection: { readonly signalIndices: readonly number[]; readonly startSeconds: number; readonly durationSeconds: number; readonly secondsPerBucket: number; }, options?: ReadOptions): Promise; //# sourceMappingURL=envelope.d.ts.map