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,53 @@
1
+ /**
2
+ * Bounds-checked slicing.
3
+ *
4
+ * Layer 0. Every offset here is a plain JS number — exact to 2^53 — and is never touched by
5
+ * `|0`, `<<`, `>>` or `>>>`. A data offset in a multi-gigabyte BDF routinely exceeds 2^31,
6
+ * where every bitwise operator silently wraps it to a negative number. (Bitwise ops are
7
+ * correct and required for SAMPLE decoding, which operates on 16- and 24-bit values.)
8
+ */
9
+ import { decodeHeaderLatin1 } from './latin1.js';
10
+ /**
11
+ * A plain `RangeError`, deliberately not `EdfRangeError`: reaching here means edfcore
12
+ * computed an offset wrongly, or a caller mixed a header with bytes from another file.
13
+ * `EdfRangeError` is reserved for the honest case of a caller asking for records that do not
14
+ * exist, and conflating the two would let an internal bug present itself as user error.
15
+ */
16
+ function assertInBounds(bytes, offset, length) {
17
+ if (!Number.isSafeInteger(offset) || offset < 0) {
18
+ throw new RangeError(`byte offset must be a non-negative safe integer, got ${offset}`);
19
+ }
20
+ if (!Number.isSafeInteger(length) || length < 0) {
21
+ throw new RangeError(`byte length must be a non-negative safe integer, got ${length}`);
22
+ }
23
+ if (offset + length > bytes.length) {
24
+ throw new RangeError(`byte range [${offset}, ${offset + length}) is outside the ${bytes.length}-byte buffer`);
25
+ }
26
+ }
27
+ /**
28
+ * A VIEW of `length` bytes at `offset`, sharing memory with `bytes` — no copy is made.
29
+ *
30
+ * Nothing in edfcore mutates a source buffer, so a view is safe to read for as long as the
31
+ * buffer lives. A caller that pools or reuses buffers must not retain the view past that
32
+ * reuse: use `copyBytes` when the bytes have to outlive the read that produced them.
33
+ */
34
+ export function sliceBytes(bytes, offset, length) {
35
+ assertInBounds(bytes, offset, length);
36
+ return bytes.subarray(offset, offset + length);
37
+ }
38
+ /** `length` bytes at `offset`, copied into a buffer of their own. */
39
+ export function copyBytes(bytes, offset, length) {
40
+ assertInBounds(bytes, offset, length);
41
+ return bytes.slice(offset, offset + length);
42
+ }
43
+ /**
44
+ * The RAW, untrimmed text of a fixed-width header field, decoded as ISO-8859-1.
45
+ *
46
+ * Untrimmed on purpose: `raw.*` on the header exposes what the file actually wrote, and the
47
+ * numeric grammars need the padding to tell a right-justified field from a conformant one.
48
+ * Callers that want the value trim it themselves with `trimEdfField`.
49
+ */
50
+ export function readAsciiField(bytes, offset, length) {
51
+ return decodeHeaderLatin1(sliceBytes(bytes, offset, length));
52
+ }
53
+ //# sourceMappingURL=view.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view.js","sourceRoot":"","sources":["../../src/bytes/view.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD;;;;;GAKG;AACH,SAAS,cAAc,CAAC,KAAiB,EAAE,MAAc,EAAE,MAAc;IACvE,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;QAChD,MAAM,IAAI,UAAU,CAAC,wDAAwD,MAAM,EAAE,CAAC,CAAC;IACzF,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;QAChD,MAAM,IAAI,UAAU,CAAC,wDAAwD,MAAM,EAAE,CAAC,CAAC;IACzF,CAAC;IACD,IAAI,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;QACnC,MAAM,IAAI,UAAU,CAClB,eAAe,MAAM,KAAK,MAAM,GAAG,MAAM,oBAAoB,KAAK,CAAC,MAAM,cAAc,CACxF,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,KAAiB,EAAE,MAAc,EAAE,MAAc;IAC1E,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,OAAO,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;AACjD,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,SAAS,CAAC,KAAiB,EAAE,MAAc,EAAE,MAAc;IACzE,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,OAAO,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;AAC9C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,KAAiB,EAAE,MAAc,EAAE,MAAc;IAC9E,OAAO,kBAAkB,CAAC,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAC/D,CAAC"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Format constants.
3
+ *
4
+ * Layer 0. Imports nothing. Every magic number the parser relies on lives here with the
5
+ * clause it comes from, so no other file needs a bare literal.
6
+ */
7
+ /** The fixed header is one of these, and each signal adds exactly one more. */
8
+ export declare const EDF_HEADER_BLOCK_BYTES: 256;
9
+ /** Total header size is always `EDF_HEADER_BLOCK_BYTES * (signalCount + 1)`. */
10
+ export declare const EDF_SIGNAL_HEADER_BYTES: 256;
11
+ /** Trimmed, case-sensitive. On disk the field is `'EDF Annotations '` — 15 chars plus a pad. */
12
+ export declare const EDF_ANNOTATIONS_LABEL: "EDF Annotations";
13
+ export declare const BDF_ANNOTATIONS_LABEL: "BDF Annotations";
14
+ /** EDF spec recommendation, not a limit. Exceeding it is a warning, never an error. */
15
+ export declare const EDF_RECOMMENDED_MAX_RECORD_BYTES: 61440;
16
+ /**
17
+ * Time is compared in exact 100 ns ticks, never in floats. Float equality on event times is
18
+ * how ERP alignment silently breaks.
19
+ */
20
+ export declare const TICKS_PER_SECOND = 10000000n;
21
+ /** 16-bit two's complement. */
22
+ export declare const EDF_DIGITAL_MIN: -32768;
23
+ export declare const EDF_DIGITAL_MAX: 32767;
24
+ /** 24-bit two's complement, sign-extended from bit 23. */
25
+ export declare const BDF_DIGITAL_MIN: -8388608;
26
+ export declare const BDF_DIGITAL_MAX: 8388607;
27
+ /** ns lives in a 4-byte field, so it cannot exceed four digits. */
28
+ export declare const EDF_MAX_SIGNAL_COUNT: 9999;
29
+ /** Default ceiling for any read that allocates. Refused before allocating, not during. */
30
+ export declare const DEFAULT_MAX_MATERIALIZE_BYTES: number;
31
+ /** TAL structural bytes. Every byte of a multi-byte UTF-8 sequence is >= 0x80, so these can
32
+ * never collide with text — which is why splitting on structure before decoding is safe. */
33
+ export declare const TAL_ONSET_DURATION_SEPARATOR: 21;
34
+ export declare const TAL_FIELD_TERMINATOR: 20;
35
+ export declare const TAL_TERMINATOR: 0;
36
+ /** Fixed-header field offsets and lengths, per the EDF specification. */
37
+ export declare const HEADER_FIELDS: {
38
+ readonly version: {
39
+ readonly offset: 0;
40
+ readonly length: 8;
41
+ };
42
+ readonly patientId: {
43
+ readonly offset: 8;
44
+ readonly length: 80;
45
+ };
46
+ readonly recordingId: {
47
+ readonly offset: 88;
48
+ readonly length: 80;
49
+ };
50
+ readonly startDate: {
51
+ readonly offset: 168;
52
+ readonly length: 8;
53
+ };
54
+ readonly startTime: {
55
+ readonly offset: 176;
56
+ readonly length: 8;
57
+ };
58
+ readonly headerByteLength: {
59
+ readonly offset: 184;
60
+ readonly length: 8;
61
+ };
62
+ readonly reserved: {
63
+ readonly offset: 192;
64
+ readonly length: 44;
65
+ };
66
+ readonly recordCount: {
67
+ readonly offset: 236;
68
+ readonly length: 8;
69
+ };
70
+ readonly recordDuration: {
71
+ readonly offset: 244;
72
+ readonly length: 8;
73
+ };
74
+ readonly signalCount: {
75
+ readonly offset: 252;
76
+ readonly length: 4;
77
+ };
78
+ };
79
+ /**
80
+ * Per-signal header field widths, in the order they appear.
81
+ *
82
+ * The layout is FIELD-MAJOR, not one struct per signal: all `ns` labels, then all `ns`
83
+ * transducers, and so on. For signal `i`, a field's address is
84
+ * `256 + ns * (sum of widths before it) + i * (its own width)`.
85
+ */
86
+ export declare const SIGNAL_FIELD_WIDTHS: {
87
+ readonly label: 16;
88
+ readonly transducerType: 80;
89
+ readonly physicalDimension: 8;
90
+ readonly physicalMinimum: 8;
91
+ readonly physicalMaximum: 8;
92
+ readonly digitalMinimum: 8;
93
+ readonly digitalMaximum: 8;
94
+ readonly prefiltering: 80;
95
+ readonly samplesPerRecord: 8;
96
+ readonly reserved: 32;
97
+ };
98
+ /** Cumulative width preceding each per-signal field. Multiply by ns to get its block start. */
99
+ export declare const SIGNAL_FIELD_BLOCK_OFFSETS: {
100
+ readonly label: 0;
101
+ readonly transducerType: 16;
102
+ readonly physicalDimension: 96;
103
+ readonly physicalMinimum: 104;
104
+ readonly physicalMaximum: 112;
105
+ readonly digitalMinimum: 120;
106
+ readonly digitalMaximum: 128;
107
+ readonly prefiltering: 136;
108
+ readonly samplesPerRecord: 216;
109
+ readonly reserved: 224;
110
+ };
111
+ /** Published package version. Kept in sync with package.json by a test. */
112
+ export declare const VERSION = "0.1.0";
113
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,+EAA+E;AAC/E,eAAO,MAAM,sBAAsB,EAAG,GAAY,CAAC;AAEnD,gFAAgF;AAChF,eAAO,MAAM,uBAAuB,EAAG,GAAY,CAAC;AAEpD,gGAAgG;AAChG,eAAO,MAAM,qBAAqB,EAAG,iBAA0B,CAAC;AAChE,eAAO,MAAM,qBAAqB,EAAG,iBAA0B,CAAC;AAEhE,uFAAuF;AACvF,eAAO,MAAM,gCAAgC,EAAG,KAAc,CAAC;AAE/D;;;GAGG;AACH,eAAO,MAAM,gBAAgB,YAAY,CAAC;AAE1C,+BAA+B;AAC/B,eAAO,MAAM,eAAe,EAAG,CAAC,KAAc,CAAC;AAC/C,eAAO,MAAM,eAAe,EAAG,KAAc,CAAC;AAE9C,0DAA0D;AAC1D,eAAO,MAAM,eAAe,EAAG,CAAC,OAAgB,CAAC;AACjD,eAAO,MAAM,eAAe,EAAG,OAAgB,CAAC;AAEhD,mEAAmE;AACnE,eAAO,MAAM,oBAAoB,EAAG,IAAa,CAAC;AAElD,0FAA0F;AAC1F,eAAO,MAAM,6BAA6B,EAAE,MAA0B,CAAC;AAEvE;6FAC6F;AAC7F,eAAO,MAAM,4BAA4B,EAAG,EAAa,CAAC;AAC1D,eAAO,MAAM,oBAAoB,EAAG,EAAa,CAAC;AAClD,eAAO,MAAM,cAAc,EAAG,CAAa,CAAC;AAE5C,yEAAyE;AACzE,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWhB,CAAC;AAEX;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;CAWtB,CAAC;AAEX,+FAA+F;AAC/F,eAAO,MAAM,0BAA0B;;;;;;;;;;;CAW7B,CAAC;AAEX,2EAA2E;AAC3E,eAAO,MAAM,OAAO,UAAU,CAAC"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Format constants.
3
+ *
4
+ * Layer 0. Imports nothing. Every magic number the parser relies on lives here with the
5
+ * clause it comes from, so no other file needs a bare literal.
6
+ */
7
+ /** The fixed header is one of these, and each signal adds exactly one more. */
8
+ export const EDF_HEADER_BLOCK_BYTES = 256;
9
+ /** Total header size is always `EDF_HEADER_BLOCK_BYTES * (signalCount + 1)`. */
10
+ export const EDF_SIGNAL_HEADER_BYTES = 256;
11
+ /** Trimmed, case-sensitive. On disk the field is `'EDF Annotations '` — 15 chars plus a pad. */
12
+ export const EDF_ANNOTATIONS_LABEL = 'EDF Annotations';
13
+ export const BDF_ANNOTATIONS_LABEL = 'BDF Annotations';
14
+ /** EDF spec recommendation, not a limit. Exceeding it is a warning, never an error. */
15
+ export const EDF_RECOMMENDED_MAX_RECORD_BYTES = 61440;
16
+ /**
17
+ * Time is compared in exact 100 ns ticks, never in floats. Float equality on event times is
18
+ * how ERP alignment silently breaks.
19
+ */
20
+ export const TICKS_PER_SECOND = 10000000n;
21
+ /** 16-bit two's complement. */
22
+ export const EDF_DIGITAL_MIN = -32768;
23
+ export const EDF_DIGITAL_MAX = 32767;
24
+ /** 24-bit two's complement, sign-extended from bit 23. */
25
+ export const BDF_DIGITAL_MIN = -8388608;
26
+ export const BDF_DIGITAL_MAX = 8388607;
27
+ /** ns lives in a 4-byte field, so it cannot exceed four digits. */
28
+ export const EDF_MAX_SIGNAL_COUNT = 9999;
29
+ /** Default ceiling for any read that allocates. Refused before allocating, not during. */
30
+ export const DEFAULT_MAX_MATERIALIZE_BYTES = 256 * 1024 * 1024;
31
+ /** TAL structural bytes. Every byte of a multi-byte UTF-8 sequence is >= 0x80, so these can
32
+ * never collide with text — which is why splitting on structure before decoding is safe. */
33
+ export const TAL_ONSET_DURATION_SEPARATOR = 0x15;
34
+ export const TAL_FIELD_TERMINATOR = 0x14;
35
+ export const TAL_TERMINATOR = 0x00;
36
+ /** Fixed-header field offsets and lengths, per the EDF specification. */
37
+ export const HEADER_FIELDS = {
38
+ version: { offset: 0, length: 8 },
39
+ patientId: { offset: 8, length: 80 },
40
+ recordingId: { offset: 88, length: 80 },
41
+ startDate: { offset: 168, length: 8 },
42
+ startTime: { offset: 176, length: 8 },
43
+ headerByteLength: { offset: 184, length: 8 },
44
+ reserved: { offset: 192, length: 44 },
45
+ recordCount: { offset: 236, length: 8 },
46
+ recordDuration: { offset: 244, length: 8 },
47
+ signalCount: { offset: 252, length: 4 },
48
+ };
49
+ /**
50
+ * Per-signal header field widths, in the order they appear.
51
+ *
52
+ * The layout is FIELD-MAJOR, not one struct per signal: all `ns` labels, then all `ns`
53
+ * transducers, and so on. For signal `i`, a field's address is
54
+ * `256 + ns * (sum of widths before it) + i * (its own width)`.
55
+ */
56
+ export const SIGNAL_FIELD_WIDTHS = {
57
+ label: 16,
58
+ transducerType: 80,
59
+ physicalDimension: 8,
60
+ physicalMinimum: 8,
61
+ physicalMaximum: 8,
62
+ digitalMinimum: 8,
63
+ digitalMaximum: 8,
64
+ prefiltering: 80,
65
+ samplesPerRecord: 8,
66
+ reserved: 32,
67
+ };
68
+ /** Cumulative width preceding each per-signal field. Multiply by ns to get its block start. */
69
+ export const SIGNAL_FIELD_BLOCK_OFFSETS = {
70
+ label: 0,
71
+ transducerType: 16,
72
+ physicalDimension: 96,
73
+ physicalMinimum: 104,
74
+ physicalMaximum: 112,
75
+ digitalMinimum: 120,
76
+ digitalMaximum: 128,
77
+ prefiltering: 136,
78
+ samplesPerRecord: 216,
79
+ reserved: 224,
80
+ };
81
+ /** Published package version. Kept in sync with package.json by a test. */
82
+ export const VERSION = '0.1.0';
83
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * De-interleaving and sign extension.
3
+ *
4
+ * Layer 3. Sole owner of turning record bytes into sample values: the 2- and 3-byte two's
5
+ * complement expressions exist here and nowhere else in edfcore.
6
+ *
7
+ * Bitwise operators appear in this file and are correct here — a sample is 16 or 24 bits, so
8
+ * `|` and `<<` are exact on it. They are never applied to an OFFSET: a data offset in a
9
+ * multi-gigabyte BDF routinely exceeds 2^31, where every bitwise operator silently wraps it
10
+ * negative. Every offset below is plain arithmetic, exact to 2^53.
11
+ */
12
+ import type { EdfHeader, RecordRange } from '../types.js';
13
+ /**
14
+ * The allocation ceiling for a decode that has to allocate.
15
+ *
16
+ * A trailing optional argument on functions whose signature is otherwise fixed by the public
17
+ * API, so passing nothing keeps the documented call shape and the 256 MiB default.
18
+ */
19
+ export interface MaterializeOptions {
20
+ readonly maxMaterializeBytes?: number;
21
+ }
22
+ export interface DecodedDigital {
23
+ readonly digital: Int32Array;
24
+ /**
25
+ * Samples outside the DECLARED digital range, counted in the same pass that decodes them so
26
+ * `EdfChunkSignal.outOfDigitalRangeCount` costs nothing. edfcore never clamps: a non-zero
27
+ * count means the declared range is wrong, not that the samples are.
28
+ */
29
+ readonly outOfDigitalRangeCount: number;
30
+ }
31
+ /**
32
+ * Decode one signal out of a record range, with the out-of-declared-range tally.
33
+ *
34
+ * The count compares against `min`/`max` of the DECLARED digital minimum and maximum rather
35
+ * than against the pair as written: an inverted declaration (`digitalMinimum > digitalMaximum`)
36
+ * would otherwise report every sample in the file as out of range, which tells the caller
37
+ * nothing about the samples.
38
+ */
39
+ export declare function decodeDigitalCounted(header: EdfHeader, recordBytes: Uint8Array, records: RecordRange, signalIndex: number, out?: Int32Array, options?: MaterializeOptions): DecodedDigital;
40
+ /**
41
+ * `recordBytes` must be exactly `records.count * header.recordByteLength` bytes and must begin
42
+ * at record `records.start`; anything else throws `EdfRangeError`. `out` is reused when
43
+ * supplied and long enough.
44
+ *
45
+ * The out-of-range tally reaches callers through `EdfChunkSignal.outOfDigitalRangeCount` — it is the same single
46
+ * pass, so a caller never needs a second one to produce it.
47
+ */
48
+ export declare function decodeDigital(header: EdfHeader, recordBytes: Uint8Array, records: RecordRange, signalIndex: number, out?: Int32Array, options?: MaterializeOptions): Int32Array;
49
+ //# sourceMappingURL=digital.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"digital.d.ts","sourceRoot":"","sources":["../../src/decode/digital.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAa,WAAW,EAAE,MAAM,aAAa,CAAC;AAIrE;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;CACvC;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;CACzC;AAiKD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,UAAU,EACvB,OAAO,EAAE,WAAW,EACpB,WAAW,EAAE,MAAM,EACnB,GAAG,CAAC,EAAE,UAAU,EAChB,OAAO,CAAC,EAAE,kBAAkB,GAC3B,cAAc,CA0BhB;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,UAAU,EACvB,OAAO,EAAE,WAAW,EACpB,WAAW,EAAE,MAAM,EACnB,GAAG,CAAC,EAAE,UAAU,EAChB,OAAO,CAAC,EAAE,kBAAkB,GAC3B,UAAU,CAEZ"}
@@ -0,0 +1,180 @@
1
+ /**
2
+ * De-interleaving and sign extension.
3
+ *
4
+ * Layer 3. Sole owner of turning record bytes into sample values: the 2- and 3-byte two's
5
+ * complement expressions exist here and nowhere else in edfcore.
6
+ *
7
+ * Bitwise operators appear in this file and are correct here — a sample is 16 or 24 bits, so
8
+ * `|` and `<<` are exact on it. They are never applied to an OFFSET: a data offset in a
9
+ * multi-gigabyte BDF routinely exceeds 2^31, where every bitwise operator silently wraps it
10
+ * negative. Every offset below is plain arithmetic, exact to 2^53.
11
+ */
12
+ import { DEFAULT_MAX_MATERIALIZE_BYTES } from '../constants.js';
13
+ import { EdfBudgetError, EdfChannelNotFoundError, EdfRangeError } from '../errors.js';
14
+ const BYTES_PER_INT32 = 4;
15
+ function assertWithinBudget(requiredBytes, what, options) {
16
+ const budgetBytes = options?.maxMaterializeBytes ?? DEFAULT_MAX_MATERIALIZE_BYTES;
17
+ if (requiredBytes <= budgetBytes)
18
+ return;
19
+ throw new EdfBudgetError(`Decoding ${what} needs a ${requiredBytes}-byte array, above the ${budgetBytes}-byte ` +
20
+ 'maxMaterializeBytes budget, so the allocation was refused before it was attempted. ' +
21
+ 'Next: decode fewer records per call, reuse an `out` array, or raise ' +
22
+ 'options.maxMaterializeBytes.', { requiredBytes, budgetBytes });
23
+ }
24
+ function signalAt(header, signalIndex) {
25
+ const signal = header.signals[signalIndex];
26
+ if (signal !== undefined)
27
+ return signal;
28
+ throw new EdfChannelNotFoundError(`signalIndex ${signalIndex} is not one of the ${header.signals.length} signals in this ` +
29
+ 'header. Next: pass an index taken from header.signals, or resolve one with ' +
30
+ 'getSignal(header, label).', { selector: signalIndex, availableLabels: header.signals.map((s) => s.label) });
31
+ }
32
+ /**
33
+ * `recordBytes` must be exactly the requested records and nothing else.
34
+ *
35
+ * Both halves matter. The range check catches a caller asking for records the file does not
36
+ * have; the length check catches a buffer that does not start where `records.start` says it
37
+ * does — which is unrecoverable rather than merely wrong, because nothing in the bytes
38
+ * identifies which record they came from.
39
+ */
40
+ function assertRecordRange(header, recordBytes, records) {
41
+ const available = { start: 0, count: header.recordCount };
42
+ const startValid = Number.isSafeInteger(records.start) && records.start >= 0;
43
+ const countValid = Number.isSafeInteger(records.count) && records.count >= 0;
44
+ if (!startValid || !countValid || records.start + records.count > header.recordCount) {
45
+ throw new EdfRangeError(`records { start: ${records.start}, count: ${records.count} } is not inside the ` +
46
+ `${header.recordCount} data records this file contains. Next: clamp the range against ` +
47
+ 'header.recordCount before decoding.', { requested: records, available });
48
+ }
49
+ const expectedBytes = records.count * header.recordByteLength;
50
+ if (recordBytes.length === expectedBytes)
51
+ return;
52
+ const wholeRecords = header.recordByteLength > 0 ? Math.floor(recordBytes.length / header.recordByteLength) : 0;
53
+ throw new EdfRangeError(`recordBytes is ${recordBytes.length} bytes, but ${records.count} records of ` +
54
+ `${header.recordByteLength} bytes each are exactly ${expectedBytes}. decodeDigital ` +
55
+ 'cannot tell which record a differently sized buffer begins at, so it will not guess. ' +
56
+ 'Next: pass the buffer returned by readRecordBytes(source, header, records) unmodified.', { requested: records, available: { start: records.start, count: wholeRecords } });
57
+ }
58
+ /**
59
+ * A plain `RangeError`, deliberately not `EdfRangeError`: a signal block that overflows its own
60
+ * record means the header's offset arithmetic is inconsistent with its record size, which is an
61
+ * edfcore bug or a hand-built header — never the caller asking for something reasonable.
62
+ */
63
+ function assertSignalFitsRecord(header, signal, blockBytes) {
64
+ if (signal.recordByteOffset + blockBytes <= header.recordByteLength)
65
+ return;
66
+ throw new RangeError(`signal ${signal.index} occupies bytes [${signal.recordByteOffset}, ` +
67
+ `${signal.recordByteOffset + blockBytes}) of a ${header.recordByteLength}-byte record, ` +
68
+ 'which does not fit. The header this was called with is internally inconsistent.');
69
+ }
70
+ /**
71
+ * The destination for `sampleCount` samples: `out` when it is large enough, otherwise a fresh
72
+ * array checked against the budget first.
73
+ *
74
+ * A longer `out` is narrowed with `subarray`, which shares its memory — the zero-allocation
75
+ * path survives — while keeping `result.length` equal to the true sample count, so no caller
76
+ * can mistake spare capacity for data.
77
+ */
78
+ function resolveOut(out, sampleCount, options) {
79
+ if (out === undefined) {
80
+ assertWithinBudget(sampleCount * BYTES_PER_INT32, `${sampleCount} digital samples`, options);
81
+ return new Int32Array(sampleCount);
82
+ }
83
+ if (out.length < sampleCount) {
84
+ throw new RangeError(`out holds ${out.length} samples but this decode produces ${sampleCount}. Next: size the ` +
85
+ 'reused array to records.count * signal.samplesPerRecord, or omit it and let ' +
86
+ 'decodeDigital allocate.');
87
+ }
88
+ return out.length === sampleCount ? out : out.subarray(0, sampleCount);
89
+ }
90
+ /** EDF: little-endian 16-bit two's complement. Returns the out-of-declared-range count. */
91
+ function decodeInt16(bytes, out, plan) {
92
+ const { recordCount, recordByteLength, recordByteOffset, samplesPerRecord } = plan;
93
+ const low = plan.digitalLow;
94
+ const high = plan.digitalHigh;
95
+ let outOfRange = 0;
96
+ let write = 0;
97
+ for (let r = 0; r < recordCount; r++) {
98
+ const base = r * recordByteLength + recordByteOffset;
99
+ for (let k = 0; k < samplesPerRecord; k++) {
100
+ const p = base + k * 2;
101
+ // In bounds by construction: assertRecordRange pinned bytes.length to
102
+ // recordCount * recordByteLength, and assertSignalFitsRecord pinned this block inside a
103
+ // record. A per-sample undefined check would cost more than the decode itself.
104
+ // biome-ignore lint/style/noNonNullAssertion: bounds are established above.
105
+ let value = bytes[p] | (bytes[p + 1] << 8);
106
+ if (value & 0x8000)
107
+ value -= 0x10000;
108
+ if (value < low || value > high)
109
+ outOfRange++;
110
+ out[write] = value;
111
+ write++;
112
+ }
113
+ }
114
+ return outOfRange;
115
+ }
116
+ /** BDF: little-endian 24-bit two's complement, sign-extended from bit 23. */
117
+ function decodeInt24(bytes, out, plan) {
118
+ const { recordCount, recordByteLength, recordByteOffset, samplesPerRecord } = plan;
119
+ const low = plan.digitalLow;
120
+ const high = plan.digitalHigh;
121
+ let outOfRange = 0;
122
+ let write = 0;
123
+ for (let r = 0; r < recordCount; r++) {
124
+ const base = r * recordByteLength + recordByteOffset;
125
+ for (let k = 0; k < samplesPerRecord; k++) {
126
+ const p = base + k * 3;
127
+ // biome-ignore lint/style/noNonNullAssertion: see decodeInt16.
128
+ let value = bytes[p] | (bytes[p + 1] << 8) | (bytes[p + 2] << 16);
129
+ if (value & 0x800000)
130
+ value -= 0x1000000;
131
+ if (value < low || value > high)
132
+ outOfRange++;
133
+ out[write] = value;
134
+ write++;
135
+ }
136
+ }
137
+ return outOfRange;
138
+ }
139
+ /**
140
+ * Decode one signal out of a record range, with the out-of-declared-range tally.
141
+ *
142
+ * The count compares against `min`/`max` of the DECLARED digital minimum and maximum rather
143
+ * than against the pair as written: an inverted declaration (`digitalMinimum > digitalMaximum`)
144
+ * would otherwise report every sample in the file as out of range, which tells the caller
145
+ * nothing about the samples.
146
+ */
147
+ export function decodeDigitalCounted(header, recordBytes, records, signalIndex, out, options) {
148
+ const signal = signalAt(header, signalIndex);
149
+ assertRecordRange(header, recordBytes, records);
150
+ const bytesPerSample = header.bytesPerSample;
151
+ const samplesPerRecord = signal.samplesPerRecord;
152
+ assertSignalFitsRecord(header, signal, samplesPerRecord * bytesPerSample);
153
+ const sampleCount = records.count * samplesPerRecord;
154
+ const digital = resolveOut(out, sampleCount, options);
155
+ const plan = {
156
+ recordCount: records.count,
157
+ recordByteLength: header.recordByteLength,
158
+ // `records.start` located the bytes; inside the buffer record 0 is at offset 0.
159
+ recordByteOffset: signal.recordByteOffset,
160
+ samplesPerRecord,
161
+ digitalLow: Math.min(signal.digitalMinimum, signal.digitalMaximum),
162
+ digitalHigh: Math.max(signal.digitalMinimum, signal.digitalMaximum),
163
+ };
164
+ const outOfDigitalRangeCount = bytesPerSample === 2
165
+ ? decodeInt16(recordBytes, digital, plan)
166
+ : decodeInt24(recordBytes, digital, plan);
167
+ return { digital, outOfDigitalRangeCount };
168
+ }
169
+ /**
170
+ * `recordBytes` must be exactly `records.count * header.recordByteLength` bytes and must begin
171
+ * at record `records.start`; anything else throws `EdfRangeError`. `out` is reused when
172
+ * supplied and long enough.
173
+ *
174
+ * The out-of-range tally reaches callers through `EdfChunkSignal.outOfDigitalRangeCount` — it is the same single
175
+ * pass, so a caller never needs a second one to produce it.
176
+ */
177
+ export function decodeDigital(header, recordBytes, records, signalIndex, out, options) {
178
+ return decodeDigitalCounted(header, recordBytes, records, signalIndex, out, options).digital;
179
+ }
180
+ //# sourceMappingURL=digital.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"digital.js","sourceRoot":"","sources":["../../src/decode/digital.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Digital counts to physical units.
3
+ *
4
+ * Layer 3. Sole owner of the scaling expression. The expression is PINNED — see `toPhysical`
5
+ * before changing anything in this file.
6
+ *
7
+ * Also home to `clampToDigitalRange`, which is a cross-validation tool and never part of a
8
+ * read: EDFlib clamps silently when it loads samples, edfcore does not, and reproducing that
9
+ * behaviour has to be something a caller asks for explicitly.
10
+ */
11
+ import type { EdfSignal } from '../types.js';
12
+ import type { MaterializeOptions } from './digital.js';
13
+ export type { MaterializeOptions } from './digital.js';
14
+ /**
15
+ * `physical = bitValue * (offset + digital)`, in float64 throughout.
16
+ *
17
+ * THIS EXPRESSION IS PINNED AND MUST NOT BE "SIMPLIFIED". It is numerically worse than
18
+ * `physicalMinimum + (digital - digitalMinimum) * gain`, and that is not an accident: it is
19
+ * EDFlib's exact form, kept verbatim so edfcore reproduces pyEDFlib/EDFlib float64 output
20
+ * bit for bit. The two forms disagree by up to ~9.3e-10 LSB — ten orders of magnitude below
21
+ * the quantisation floor, and on asymmetric ranges they differ on nearly half the samples by
22
+ * one ULP. Rewriting this line breaks the golden-value tests, and rightly so.
23
+ *
24
+ * Throws `EdfScalingError` when `signal.scale` is `undefined`. edfcore never fabricates a gain,
25
+ * and `decodeDigital` keeps working on such a signal.
26
+ */
27
+ export declare function toPhysical(signal: EdfSignal, digital: ArrayLike<number>, out?: Float64Array, options?: MaterializeOptions): Float64Array;
28
+ /**
29
+ * Clamp to the declared digital range. POST-HOC ONLY — nothing on the read path calls this.
30
+ *
31
+ * It exists to reproduce a clamping consumer (EDFlib clamps silently on read; edfcore does not)
32
+ * when cross-validating against one. Clamping to `[min(digMin, digMax), max(digMin, digMax)]`
33
+ * rather than to `[digMin, digMax]` matters for an inverted declaration, where the naive bounds
34
+ * are empty and collapse every sample onto a single value.
35
+ */
36
+ export declare function clampToDigitalRange(signal: EdfSignal, digital: Int32Array, out?: Int32Array, options?: MaterializeOptions): Int32Array;
37
+ //# sourceMappingURL=physical.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"physical.d.ts","sourceRoot":"","sources":["../../src/decode/physical.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAqB,SAAS,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,YAAY,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAsJvD;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,SAAS,CAAC,MAAM,CAAC,EAC1B,GAAG,CAAC,EAAE,YAAY,EAClB,OAAO,CAAC,EAAE,kBAAkB,GAC3B,YAAY,CAad;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,UAAU,EACnB,GAAG,CAAC,EAAE,UAAU,EAChB,OAAO,CAAC,EAAE,kBAAkB,GAC3B,UAAU,CAsBZ"}