/** * Digital counts to physical units. * * Layer 3. Sole owner of the scaling expression. The expression is PINNED — see `toPhysical` * before changing anything in this file. * * Also home to `clampToDigitalRange`, which is a cross-validation tool and never part of a * read: EDFlib clamps silently when it loads samples, edfcore does not, and reproducing that * behaviour has to be something a caller asks for explicitly. */ import { EdfScalingError } from '../errors.js'; import type { EdfSignal } from '../types.js'; import type { MaterializeOptions } from './digital.js'; export type { MaterializeOptions } from './digital.js'; /** * The one place a "this signal has no scale" refusal is built. * * Exported because `toPhysicalEnvelope` refuses the same signals for the same reasons. It used to * hard-code `{ code: 'SCALE_UNAVAILABLE' }`, so the two published entry points answered "why has * this signal no scale?" with different codes for one signal — and `SCALE_UNAVAILABLE` is defined * as the case where none of the other conditions applies, which made it positively false for a * signal the header had already diagnosed `DEGENERATE_DIGITAL_RANGE`. The envelope message also * carried no raw fields and no spec reference (fixed in 0.3.111). * * `consequence` and `nextStep` are what genuinely differ between the two: a sample read tells you * `decodeDigital()` still works, an envelope tells you to plot the digital one. The cause, the * code, the re-derivation ORDER and the evidence stay here. */ export declare function scalingError(signal: EdfSignal, tail?: { readonly consequence?: string; readonly nextStep?: string; }): EdfScalingError; /** * `physical = bitValue * (offset + digital)`, in float64 throughout. * * THIS EXPRESSION IS PINNED AND MUST NOT BE "SIMPLIFIED". It is numerically worse than * `physicalMinimum + (digital - digitalMinimum) * gain`, and that is not an accident: it is * EDFlib's exact form, kept verbatim so edfcore reproduces pyEDFlib/EDFlib float64 output * bit for bit. The two forms disagree by up to ~9.3e-10 LSB — ten orders of magnitude below * the quantisation floor, and on asymmetric ranges they differ on nearly half the samples by * one ULP. Rewriting this line breaks the golden-value tests, and rightly so. * * Throws `EdfScalingError` when `signal.scale` is `undefined`. edfcore never fabricates a gain, * and `decodeDigital` keeps working on such a signal. */ /** * The SIGNAL, before any field of it is read. * * `EdfChunkSignal` is the other per-signal shape and it is the one a reader has in hand after a * read, so `toPhysical(chunk.signals[0], chunk.signals[0].digital)` is the call the arguments * suggest. It has no `scale`, so `toPhysical` took the no-gain branch and `scalingError` then read * `signal.raw.digitalMinimum` off it — V8's `Cannot read properties of undefined`, thrown from * inside the builder of the error that was meant to explain the problem. * * `physicalRangeOf` got further, and worse. It reported `signal undefined "undefined" declares * physical minimum "undefined"` and sent the reader to `header.diagnostics` for a signal that is not * there: a complaint about the FILE for a mistake in the argument, which is the one confusion * `byteSource` says this package works hardest to avoid (fixed in 0.6.104). */ export declare function assertSignal(signal: EdfSignal, call: string): void; export declare function toPhysical(signal: EdfSignal, digital: ArrayLike, out?: Float64Array, options?: MaterializeOptions): Float64Array; /** * The signal's declared physical bounds, in ascending order. * * `signal.physicalMinimum` is not the smaller of the two. A negative amplifier gain is declared * by putting the larger value in the minimum field, it is legal, and edfcore never "fixes" it — * so `{ low: signal.physicalMinimum, high: signal.physicalMaximum }` written by hand gives a * viewer an inverted y-axis on exactly the channels where the trace is also inverted, and the * two mistakes hide each other. * * This is the DECLARED envelope, not the observed one: samples outside it exist (that is what * `outOfDigitalRangeCount` counts) and this function does not look at any. It is what a fixed * axis or a gain control should be built from. * * Throws `RangeError` when either bound is not finite, for the reason `clampToDigitalRange` * does: `Math.min(NaN, x)` is `NaN`, and an axis from `NaN` to `NaN` draws nothing while * reporting no error at all. */ export declare function physicalRangeOf(signal: EdfSignal): { readonly low: number; readonly high: number; }; /** * Clamp to the declared digital range. POST-HOC ONLY — nothing on the read path calls this. * * It exists to reproduce a clamping consumer (EDFlib clamps silently on read; edfcore does not) * when cross-validating against one. Clamping to `[min(digMin, digMax), max(digMin, digMax)]` * rather than to `[digMin, digMax]` matters for an inverted declaration, where the naive bounds * are empty and collapse every sample onto a single value. */ export declare function clampToDigitalRange(signal: EdfSignal, digital: Int32Array, out?: Int32Array, options?: MaterializeOptions): Int32Array; //# sourceMappingURL=physical.d.ts.map