@fishka/seqio 0.2.0 → 0.4.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.
@@ -7,10 +7,35 @@
7
7
  interface AbifFile {
8
8
  /** ABIF version, e.g. 101 for v1.01. */
9
9
  version: number;
10
+ /** The root directory (tdir) header, exactly as read — including any directory padding it declares. */
11
+ tdir: AbifDirectory;
10
12
  /** Directory entries in the original on-disk order. */
11
13
  entries: AbifEntry[];
12
14
  /** MacBinary preamble offset (128 if present, 0 otherwise). Preserved for diagnostics. */
13
15
  macBinaryOffset: number;
16
+ /**
17
+ * The 128-byte MacBinary preamble, verbatim — present only when {@link macBinaryOffset} is 128.
18
+ * Exposed so a raw reader can reproduce the wrapper, not just note that it existed.
19
+ */
20
+ macBinaryHeader?: Uint8Array;
21
+ /**
22
+ * Reserved header bytes [34..127] (94 bytes), verbatim. Usually zeros; exposed so a raw reader
23
+ * keeps the entire 128-byte header, not just magic/version/tdir.
24
+ */
25
+ headerReserved: Uint8Array;
26
+ /**
27
+ * Physical byte ranges not covered by the header, directory, or any entry payload — orphaned
28
+ * blocks left by editing tools, trailing padding, etc. Empty for a tightly-packed file. Exposed
29
+ * so a raw reader accounts for every byte; the chromatogram never depends on these.
30
+ */
31
+ unreferencedRanges: AbifByteRange[];
32
+ }
33
+ /** A contiguous run of file bytes at an absolute offset — used for {@link AbifFile.unreferencedRanges}. */
34
+ interface AbifByteRange {
35
+ /** Absolute offset from the start of the file (includes any MacBinary preamble). */
36
+ offset: number;
37
+ /** The bytes in this range, verbatim. `bytes.length` is the range length. */
38
+ bytes: Uint8Array;
14
39
  }
15
40
  interface AbifEntry {
16
41
  /** 4-character tag name, e.g. "DATA", "PBAS". */
@@ -23,12 +48,72 @@ interface AbifEntry {
23
48
  elementType: number;
24
49
  /** Bytes per element. */
25
50
  elementSize: number;
26
- /** Number of elements. */
51
+ /** Number of elements (reconciled: clamped to what the payload holds). */
27
52
  elementCount: number;
28
- /** Raw payload bytes (length === elementCount * elementSize). */
53
+ /**
54
+ * Raw payload bytes. Length === the on-disk `dataSize` (the authoritative field), which for
55
+ * user/opaque types may differ from `elementCount * elementSize`.
56
+ */
29
57
  payload: Uint8Array;
30
58
  /** Opaque dataHandle field, usually 0. Preserved for round-trip. */
31
59
  dataHandle: number;
60
+ /**
61
+ * The directory fields exactly as they were on disk — present only when this entry was read from a
62
+ * file (absent for entries synthesized by setters/writeAbif). Lets consumers inspect the real record
63
+ * without our reconciliation.
64
+ */
65
+ raw?: AbifEntryRaw;
66
+ }
67
+ /** A directory entry's on-disk fields, verbatim, before any reconciliation applied by {@link readAbif}. */
68
+ interface AbifEntryRaw {
69
+ /** `elementCount` (numElements) as written on disk. */
70
+ elementCount: number;
71
+ /** `dataSize` field as written on disk, in bytes. */
72
+ dataSize: number;
73
+ /** External payload offset relative to the ABIF start, or -1 when the payload is inline. */
74
+ dataOffset: number;
75
+ /** Whether the payload was stored inline (declared dataSize ≤ 4). */
76
+ inline: boolean;
77
+ /**
78
+ * The 4 raw bytes of the dataOffset/data slot, verbatim. For an external entry these are the
79
+ * big-endian offset; for an inline entry they are the value bytes plus any padding/stale bytes
80
+ * beyond `dataSize` — exposed so a raw reader loses no structure.
81
+ */
82
+ dataOffsetBytes: Uint8Array;
83
+ }
84
+ /**
85
+ * The root directory (`tdir`) header — the header's own directory entry, describing the directory
86
+ * block. Its fields are exposed like any other raw entry so a raw reader never loses them, even when
87
+ * `rawEntryCount` desyncs from `dataSize`.
88
+ */
89
+ interface AbifDirectory {
90
+ /** Effective number of entries actually read — equals `entries.length`. */
91
+ entryCount: number;
92
+ /** tdir `numElements` verbatim, before reconciliation; the authoritative on-disk entry count. */
93
+ rawEntryCount: number;
94
+ /** tdir `elementType` (1023). */
95
+ elementType: number;
96
+ /** tdir `tagNumber` (usually 1). */
97
+ tagNumber: number;
98
+ /** Bytes per directory entry (always 28). */
99
+ entrySize: number;
100
+ /**
101
+ * Directory block size in bytes, from the tdir `dataSize` field. May exceed `entryCount * 28`
102
+ * when the file carries directory padding / extra bytes.
103
+ */
104
+ dataSize: number;
105
+ /** File offset (relative to the ABIF start) where the directory block begins. */
106
+ dataOffset: number;
107
+ /** The 4 raw bytes of the tdir's dataOffset field, verbatim. */
108
+ dataOffsetBytes: Uint8Array;
109
+ /** tdir `dataHandle` field, usually 0. */
110
+ dataHandle: number;
111
+ /**
112
+ * The raw directory-padding bytes: everything from the end of the last entry to the end of the
113
+ * directory block (`dataSize - entryCount*28`). Usually zeros; exposed so a raw reader keeps the
114
+ * whole directory structure, not just the entries.
115
+ */
116
+ paddingBytes: Uint8Array;
32
117
  }
33
118
  interface ChannelSignals {
34
119
  A: number[];
@@ -36,6 +121,8 @@ interface ChannelSignals {
36
121
  G: number[];
37
122
  T: number[];
38
123
  }
124
+ /** Spec-defined role of a DATA<n> tag — see {@link dataChannelRole}. */
125
+ type AbifDataChannelRole = 'trace' | 'telemetry' | 'other';
39
126
  /** Per-base chromatogram view. */
40
127
  interface Chromatogram {
41
128
  /** Per-base peak position in sample points (from PLOC). */
@@ -47,7 +134,39 @@ interface Chromatogram {
47
134
  /** Average peak spacing (samples per base), from SPAC. */
48
135
  samplingRate?: number;
49
136
  }
50
- /** Basecalls extracted from PBAS/PCON/PLOC under the chosen version. */
137
+ /**
138
+ * One basecall version exactly as the file stores it. ABIF numbers the
139
+ * PBAS/PCON/PLOC tags by version, and the spec fixes what each number means:
140
+ *
141
+ * - version 2 (`role: 'called'`) — the sequence as produced by the basecaller.
142
+ * - version 1 (`role: 'edited'`) — the sequence after user hand-editing.
143
+ * - any other version (`role: 'unknown'`) — the spec only defines 1 and 2, so
144
+ * a vendor/future PBAS3+ is surfaced without a claimed role.
145
+ *
146
+ * A file may carry either or both, and the two can differ in content and even in
147
+ * length (edits insert/delete bases). {@link parseAbif} exposes every version it
148
+ * finds via {@link ParsedAbif.baseCallVariants} — picking which one to show or
149
+ * export is the consumer's call, not the parser's.
150
+ */
151
+ type AbifBaseCallRole = 'called' | 'edited' | 'unknown';
152
+ interface AbifBaseCallVariant {
153
+ /** Tag number of the PBAS/PCON/PLOC this variant came from (1, 2, or a vendor number). */
154
+ version: number;
155
+ /** Spec-defined role by tag number: 2 = basecaller-called, 1 = user-edited, else unknown. */
156
+ role: AbifBaseCallRole;
157
+ /** Bases from PBAS<version> as stored — case preserved, trailing NULs stripped, not normalized. */
158
+ sequence: string;
159
+ /** Per-base Q-scores from PCON<version>; `[]` when that version has no PCON. */
160
+ confidences: number[];
161
+ /** Per-base peak positions (sample indices) from PLOC<version>; `[]` when absent. */
162
+ positions: number[];
163
+ }
164
+ /**
165
+ * Convenience pointer to the preferred basecall version — the called one (PBAS2)
166
+ * when present, else whatever single version the file has. This is a spec-role
167
+ * choice (called over edited), not a quality judgement; see
168
+ * {@link ParsedAbif.baseCallVariants} for every version the file actually carries.
169
+ */
51
170
  interface AbifBaseCalls {
52
171
  /** Called bases (uppercase). */
53
172
  sequence: string;
@@ -68,34 +187,37 @@ interface AbifMetadata {
68
187
  runDate?: string;
69
188
  runTime?: string;
70
189
  samplingRate?: number;
190
+ /**
191
+ * Value of the RevC1 flag: whether the file declares its sequence already
192
+ * reverse-complemented. undefined when the tag is absent. Reported as-is — the
193
+ * consumer decides what to do with it.
194
+ */
195
+ reverseComplemented?: boolean;
71
196
  comments: string[];
72
197
  }
73
198
  /**
74
- * Full chromatogram bundle: all DATA channels, plus FWO_-aware A/C/G/T views.
75
- *
76
- * The two block views are named after their on-disk tag number ranges. Their
77
- * semantic content varies by instrument:
78
- *
79
- * - DATA1..4 — on modern KB-basecaller-aware instruments (3130, 3500,
80
- * 3730) these are POST-PROCESSED traces (mobility-corrected,
81
- * baseline-subtracted, color-separated). Basecallers operate
82
- * on these. On older simpler instruments DATA1..4 IS the raw
83
- * fluorescence.
84
- * - DATA9..12 RAW fluorescence on instruments that produce both blocks.
85
- * Absent on instruments that only write DATA1..8.
86
- *
87
- * Use {@link hasProcessedTraces} (data9To12 present) to detect whether
88
- * DATA1..4 has already been processed and bypass your own baseline/color
89
- * steps accordingly.
199
+ * All DATA channels plus FWO_-aware A/C/G/T views of the two dye-trace blocks.
200
+ *
201
+ * The block views are named after their on-disk tag ranges — nothing more. Which
202
+ * block is "raw" and which is "analyzed / processed" is an instrument-and-tool
203
+ * convention that the ABIF file does NOT state, so this parser refuses to label
204
+ * it. The consumer that needs to tell them apart has the primitives to decide:
205
+ * the per-block sample counts (channel lengths) and the basecall peak positions
206
+ * ({@link AbifBaseCalls.positions}); e.g. positions that overflow one block's
207
+ * length can only belong to the other.
208
+ *
209
+ * Per the ABIF spec, DATA5..8 are instrument telemetry (voltage / current / power
210
+ * / temperature), NOT dye traces — see {@link dataChannelRole}. They live in
211
+ * {@link dataChannels} but are excluded from the A/C/G/T views.
90
212
  */
91
213
  interface AbifChromatogramBundle {
92
214
  /** FWO_ value (e.g. "GATC"). */
93
215
  baseOrder: string;
94
- /** All DATA tags by tagNumber → trace. */
216
+ /** All DATA tags by tagNumber → trace (dye traces AND telemetry). */
95
217
  dataChannels: Record<number, number[]>;
96
- /** DATA1..4 mapped to A/C/G/T by FWO_. Post-processed on newer instruments, raw on older. */
218
+ /** DATA1..4 mapped to A/C/G/T by FWO_. */
97
219
  data1To4: ChannelSignals;
98
- /** DATA9..12 mapped to A/C/G/T by FWO_. Raw fluorescence; absent on older instruments. */
220
+ /** DATA9..12 mapped to A/C/G/T by FWO_; empty when the file has no 9..12 block. */
99
221
  data9To12: ChannelSignals;
100
222
  }
101
223
  /** Rich result from {@link parseAbif} — everything the typical viewer needs. */
@@ -107,7 +229,10 @@ interface ParsedAbif {
107
229
  dirEntryCount: number;
108
230
  metadata: AbifMetadata;
109
231
  chromatogram: AbifChromatogramBundle;
232
+ /** Preferred basecall version (called over edited); convenience over {@link baseCallVariants}. */
110
233
  baseCalls?: AbifBaseCalls;
234
+ /** Every basecall version the file carries (called and/or edited), in version order. */
235
+ baseCallVariants: AbifBaseCallVariant[];
111
236
  /** All directory entries with their decoded payloads (best-effort by type). */
112
237
  entries: AbifDirEntry[];
113
238
  }
@@ -119,9 +244,15 @@ interface AbifDirEntry {
119
244
  elementType: number;
120
245
  elementTypeName: string;
121
246
  elementSize: number;
247
+ /** Reconciled element count (clamped to the declared dataSize when it was smaller). */
122
248
  elementCount: number;
249
+ /** `numElements` exactly as written on disk, before reconciliation. */
250
+ rawElementCount: number;
251
+ /** `dataSize` as written on disk, in bytes (not recomputed). */
123
252
  dataSize: number;
253
+ /** External payload offset relative to the ABIF start, or -1 when the payload is inline. */
124
254
  dataOffset: number;
255
+ /** Whether the payload was stored inline (declared dataSize ≤ 4). */
125
256
  inline: boolean;
126
257
  decoded: AbifDecodedValue;
127
258
  preview: string;
@@ -184,12 +315,14 @@ declare function tagNameFromInt32(n: number): string;
184
315
  * 8 2 elementType (int16)
185
316
  * 10 2 elementSize (int16)
186
317
  * 12 4 elementCount (int32)
187
- * 16 4 dataSize (int32; total payload bytes; spec says count*size)
318
+ * 16 4 dataSize (int32; total payload bytes authoritative; for
319
+ * well-formed entries equals count*size, but user/
320
+ * opaque types may differ, so we read by dataSize)
188
321
  * 20 4 dataOffset (int32) OR inline data if dataSize <= 4
189
322
  * 24 4 dataHandle (int32; usually 0)
190
323
  *
191
- * Inline rule (per ABIF spec): when count*size <= 4 the payload bytes are
192
- * stored directly in the dataOffset field (left-aligned, padded to 4 bytes).
324
+ * Inline rule (per ABIF spec): when the declared dataSize <= 4 the payload bytes
325
+ * are stored directly in the dataOffset field (left-aligned, padded to 4 bytes).
193
326
  *
194
327
  * MacBinary preamble: some ABIF files (older Mac-origin) start with a 128-byte
195
328
  * MacBinary header before the actual ABIF magic. We detect and skip it.
@@ -198,7 +331,13 @@ declare function tagNameFromInt32(n: number): string;
198
331
  declare const HEADER_SIZE = 128;
199
332
  declare const ENTRY_SIZE = 28;
200
333
  /**
201
- * Parse an ABIF file from raw bytes.
334
+ * Parse an ABIF file from raw bytes into its verbatim directory structure.
335
+ *
336
+ * This is the RAW source of truth: it reads the tdir header and every entry as
337
+ * stored — real dataSize, dataOffset, numElements (see `entry.raw`), the 4 inline
338
+ * bytes, and the tdir/directory metadata — and interprets nothing. For a
339
+ * high-level, opinionated view (typed channels, basecalls, metadata) layer
340
+ * {@link parseAbif} on top.
202
341
  *
203
342
  * Accepts Uint8Array (works in Node and the browser). Node's Buffer extends
204
343
  * Uint8Array, so `readAbif(buffer)` and `readAbif(new Uint8Array(arrayBuffer))`
@@ -212,14 +351,13 @@ declare function readAbif(bytes: Uint8Array): AbifFile;
212
351
  * Serialize an AbifFile back to a Uint8Array.
213
352
  *
214
353
  * Layout produced: header (128 B) + directory (N*28 B) + payload block.
215
- * External payloads are packed tightly in entry order. Inline payloads
216
- * (count*size <= 4) live entirely inside their directory entry.
217
- *
218
- * The output is not bit-identical to the input if the original had a different
219
- * physical layout (e.g. payloads before directory), but the *meaning*
220
- * round-trips: readAbif(writeAbif(f)) reproduces the same entries structurally.
354
+ * External payloads are packed tightly in entry order. Payloads <= 4 bytes are
355
+ * stored inline inside their directory entry.
221
356
  *
222
- * MacBinary preamble is not preserved by writeAbif.
357
+ * Meaning-lossless, not byte-exact: the payload bytes, tag fields and dataSize
358
+ * (= payload length) round-trip, but physical layout does not — payloads are
359
+ * repacked, directory/header padding and any MacBinary preamble are dropped. A
360
+ * byte-exact layout-preserving mode is a possible future opt-in.
223
361
  */
224
362
  declare function writeAbif(file: AbifFile): Uint8Array;
225
363
  /** Find an entry by name+number, or undefined. */
@@ -254,33 +392,46 @@ declare function getDataChannel(file: AbifFile, n: number): Int16Array | undefin
254
392
  */
255
393
  declare function getFwo(file: AbifFile): string;
256
394
  /**
257
- * True when the file carries BOTH DATA1..4 and DATA9..12. Newer ABI-style
258
- * instruments produce both: DATA1..4 is post-processed (mobility-corrected,
259
- * baseline-subtracted, color-separated) and DATA9..12 is raw fluorescence.
260
- * Older instruments produce only DATA1..4 (which IS the raw signal).
261
- *
262
- * Callers that re-process traces (basecallers) should detect this and bypass
263
- * their own baseline-subtraction / color-matrix steps when DATA1..4 is
264
- * already cleaned.
395
+ * Whether an FWO_ string is a genuine dye order: a permutation of A/C/G/T with all
396
+ * four bases distinct. `"GATC"` passes; `"AAAA"` (regex-valid but degenerate) does
397
+ * not mapping four channels through it would collapse them onto one base.
265
398
  */
266
- declare function hasProcessedTraces(file: AbifFile): boolean;
399
+ declare function isFwoPermutation(fwo: string): boolean;
267
400
  /**
268
- * Map from base letter ("A"|"C"|"G"|"T") to the DATA tag number that holds
269
- * its RAW fluorescence channel.
401
+ * Fact: the file carries a second dye-trace block, DATA9..12 (all four tags), in
402
+ * addition to DATA1..4. Reports presence only — it does NOT say which block is
403
+ * raw and which is analyzed/processed; that convention isn't stored in the file.
404
+ */
405
+ declare function hasData9To12Block(file: AbifFile): boolean;
406
+ /**
407
+ * Spec-defined role of a DATA<n> tag, limited to the numbers the ABIF spec names:
270
408
  *
271
- * ABIF stores up to 12 DATA tags. KB-basecaller-aware instruments (3130, 3500,
272
- * ...) use DATA1..4 for processed traces and DATA9..12 for raw. Older / simpler
273
- * instruments produce only DATA1..8 where DATA1..4 ARE the raw traces.
409
+ * - `'trace'` — dye-signal channels: DATA1..4 (raw dyes 1-4), DATA9..12
410
+ * (analyzed dyes 1-4), and the two optional 5th-dye blocks
411
+ * DATA105 (raw dye 5) and DATA205 (analyzed dye 5).
412
+ * - `'telemetry'` — DATA5..8: instrument run telemetry (voltage, current,
413
+ * power, temperature), one value per scan — NOT dye signal.
414
+ * - `'other'` — any other DATA number. The spec does not enumerate higher
415
+ * extra-dye tags (106/206/…), so we don't claim a role for them.
274
416
  *
275
- * This helper always returns the DATA1..4 mapping (the convention used by
276
- * basecallers operating on raw signal). Use {@link hasProcessedTraces} to
277
- * detect whether DATA1..4 has been pre-processed and bypass baseline/color
278
- * steps accordingly.
417
+ * A fact from the ABIF specification, not an inference about content.
418
+ */
419
+ declare function dataChannelRole(n: number): AbifDataChannelRole;
420
+ /**
421
+ * Whether the file declares its sequence already reverse-complemented, from the
422
+ * RevC1 flag (int16, non-zero = true). undefined when RevC1 is absent. Reported
423
+ * as-is; the consumer decides how to act on it.
424
+ */
425
+ declare function getReverseComplemented(file: AbifFile): boolean | undefined;
426
+ /**
427
+ * Map from base letter ("A"|"C"|"G"|"T") to the DATA1..4 tag number that holds
428
+ * its channel, using the dye order declared by FWO_. If FWO_="GATC" then
429
+ * G→1, A→2, T→3, C→4.
279
430
  *
280
- * The dye-to-channel order within each block is given by FWO_. If
281
- * FWO_="GATC" then G→1, A→2, T→3, C→4.
431
+ * This follows the file's own FWO_ declaration for the DATA1..4 block; it makes
432
+ * no claim about whether that block is raw or processed.
282
433
  */
283
- declare function getRawChannelMap(file: AbifFile): Record<'A' | 'C' | 'G' | 'T', number>;
434
+ declare function getChannelMap(file: AbifFile): Record<'A' | 'C' | 'G' | 'T', number>;
284
435
  /** Get PBAS sequence (prefer PBAS2 over PBAS1). undefined if neither present. */
285
436
  declare function getSequence(file: AbifFile): string | undefined;
286
437
  /**
@@ -374,6 +525,12 @@ declare function ensureRawDataChannels(file: AbifFile): void;
374
525
  * Parse an ABIF file into a high-level view: metadata, channels, basecalls,
375
526
  * and decoded directory entries.
376
527
  *
528
+ * This is the INTERPRETING layer — it makes convenience choices (FWO_ → "GATC"
529
+ * fallback, derived samplingRate, preferred/upper-cased PBAS2 baseCalls). It is
530
+ * not the raw structural truth: for that use {@link readAbif}, which reads the
531
+ * directory verbatim (raw dataSize/offset/counts, tdir, inline bytes) and makes
532
+ * no interpretation. `parseAbif` builds on top of it.
533
+ *
377
534
  * Accepts ArrayBuffer or Uint8Array (Buffer in Node works too — it extends
378
535
  * Uint8Array). The `fileName` argument is informational only; it's preserved
379
536
  * in the result.
@@ -384,4 +541,4 @@ declare function hasSignals(s: ChannelSignals): boolean;
384
541
  /** Length of the longest channel in a ChannelSignals bundle. */
385
542
  declare function channelMaxLength(s: ChannelSignals): number;
386
543
 
387
- export { type AbifBaseCalls, type AbifChromatogramBundle, type AbifDecodedValue, type AbifDirEntry, type AbifEntry, type AbifFile, type AbifMetadata, type ChannelSignals, type Chromatogram, ENTRY_SIZE, HEADER_SIZE, type ParsedAbif, averagePeakSpacing, channelMaxLength, ensureRawDataChannels, findEntries, findEntry, getConfidences, getDataChannel, getFwo, getPositions, getRawChannelMap, getSamplingRate, getSequence, hasProcessedTraces, hasSignals, parseAbif, readAbif, setAveragePeakSpacing, setConfidences, setPositions, setSequence, tagNameFromInt32, upsertEntry, writeAbif };
544
+ export { type AbifBaseCallRole, type AbifBaseCallVariant, type AbifBaseCalls, type AbifByteRange, type AbifChromatogramBundle, type AbifDataChannelRole, type AbifDecodedValue, type AbifDirEntry, type AbifDirectory, type AbifEntry, type AbifEntryRaw, type AbifFile, type AbifMetadata, type ChannelSignals, type Chromatogram, ENTRY_SIZE, HEADER_SIZE, type ParsedAbif, averagePeakSpacing, channelMaxLength, dataChannelRole, ensureRawDataChannels, findEntries, findEntry, getChannelMap, getConfidences, getDataChannel, getFwo, getPositions, getReverseComplemented, getSamplingRate, getSequence, hasData9To12Block, hasSignals, isFwoPermutation, parseAbif, readAbif, setAveragePeakSpacing, setConfidences, setPositions, setSequence, tagNameFromInt32, upsertEntry, writeAbif };
@@ -1,2 +1,2 @@
1
- 'use strict';function y(e){let t="";for(let n=0;n<e.length;n++)t+=String.fromCharCode(e[n]);return t}function x(e){let t=new Uint8Array(e.length);for(let n=0;n<e.length;n++)t[n]=e.charCodeAt(n)&255;return t}function p(e){return new DataView(e.buffer,e.byteOffset,e.byteLength)}function C(e,t,n){return e.subarray(t,t+n)}function P(e){return String.fromCharCode(e>>>24&255,e>>>16&255,e>>>8&255,e&255)}var F=128,w=28;function U(e){if(e.byteLength<F)throw new Error(`ABIF too small: ${e.byteLength} bytes`);let t=0;if(y(C(e,0,4))!=="ABIF")if(e.byteLength>=132&&y(C(e,128,4))==="ABIF")t=128;else throw new Error('Not an ABIF file: missing "ABIF" magic.');let n=t,r=p(e),a=r.getInt16(n+4,false),i=k(e,r,n+6);if(i.tagName!=="tdir")throw new Error(`Expected "tdir" header entry, got "${i.tagName}"`);if(i.elementSize!==w)throw new Error(`Expected dir element size ${w}, got ${i.elementSize}`);let c=i.elementCount,m=r.getInt32(n+6+20,false);if(m<0||m+c*w>e.byteLength-n)throw new Error(`Directory out of bounds: offset=${m}, entries=${c}, file=${e.byteLength-n}`);let d=[];for(let o=0;o<c;o++)d.push(k(e,r,n+m+o*w));return {version:a,entries:d,macBinaryOffset:t}}function k(e,t,n){let r=y(C(e,n,4)),a=t.getInt32(n+4,false),i=t.getInt16(n+8,false),c=t.getInt16(n+10,false),m=t.getInt32(n+12,false),d=t.getInt32(n+16,false),o=t.getInt32(n+24,false),g=Math.max(0,m*Math.max(0,c)),h=Math.min(g,Math.max(0,d)),N=g<=4,$=c>0&&d>=0&&d<g?Math.floor(d/c):m,S;if(N)S=new Uint8Array(C(e,n+20,h));else {let A=t.getInt32(n+20,false);if(A<0||A+h>e.byteLength)throw new Error(`Entry ${r}${a}: payload out of bounds (offset=${A}, size=${h})`);S=new Uint8Array(C(e,A,h));}return {tagName:r,tagNumber:a,elementType:i,elementSize:c,elementCount:$,payload:S,dataHandle:o}}function H(e){let t=e.entries.length,n=F,r=t*w,a=n+r,i=new Array(t);for(let o=0;o<t;o++){let g=e.entries[o],h=g.elementCount*g.elementSize;if(g.payload.byteLength!==h)throw new Error(`Entry ${g.tagName}${g.tagNumber}: payload length ${g.payload.byteLength} != dataSize ${h}`);h>4?(i[o]=a,a+=h):i[o]=-1;}let c=a,m=new Uint8Array(c),d=p(m);m.set(x("ABIF"),0),d.setInt16(4,e.version,false),z({tagName:"tdir",tagNumber:1,elementType:1023,elementSize:w,elementCount:t,payload:new Uint8Array(0),dataHandle:0},m,d,6,-1,r,n);for(let o=0;o<t;o++){let g=e.entries[o];z(g,m,d,n+o*w,i[o]);}for(let o=0;o<t;o++){let g=i[o];g>=0&&m.set(e.entries[o].payload,g);}return m}function z(e,t,n,r,a,i,c){if(e.tagName.length!==4)throw new Error(`tagName must be 4 chars: "${e.tagName}"`);t.set(x(e.tagName),r),n.setInt32(r+4,e.tagNumber,false),n.setInt16(r+8,e.elementType,false),n.setInt16(r+10,e.elementSize,false),n.setInt32(r+12,e.elementCount,false);let m=i??e.elementCount*e.elementSize;if(n.setInt32(r+16,m,false),c!==void 0)n.setInt32(r+20,c,false);else if(m<=4)t.fill(0,r+20,r+24),t.set(e.payload.subarray(0,m),r+20);else {if(a<0)throw new Error(`External payload requires offset for ${e.tagName}${e.tagNumber}`);n.setInt32(r+20,a,false);}n.setInt32(r+24,e.dataHandle,false);}function b(e,t,n){return e.entries.find(r=>r.tagName===t&&r.tagNumber===n)}function _(e,t){return e.entries.filter(n=>n.tagName===t)}function v(e,t,n,r,a){if(r.byteLength!==a.elementCount*a.elementSize)throw new Error(`upsertEntry ${t}${n}: payload length ${r.byteLength} != count*size`);let i=b(e,t,n);i?(i.elementType=a.elementType,i.elementSize=a.elementSize,i.elementCount=a.elementCount,i.payload=r):e.entries.push({tagName:t,tagNumber:n,elementType:a.elementType,elementSize:a.elementSize,elementCount:a.elementCount,payload:r,dataHandle:0});}function Z(e,t){let n=b(e,"DATA",t);if(!n)return;if(n.elementSize!==2)throw new Error(`DATA${t}: expected elementSize=2, got ${n.elementSize}`);let r=new Int16Array(n.elementCount),a=p(n.payload);for(let i=0;i<n.elementCount;i++)r[i]=a.getInt16(i*2,false);return r}function T(e){let t=b(e,"FWO_",1);return t?y(t.payload):"GATC"}function I(e){return !!(b(e,"DATA",9)&&b(e,"DATA",10)&&b(e,"DATA",11)&&b(e,"DATA",12))}function J(e){let t=T(e);if(!/^[ACGT]{4}$/.test(t))throw new Error(`FWO_ malformed: "${t}"`);return {[t[0]]:1,[t[1]]:2,[t[2]]:3,[t[3]]:4}}function K(e){let t=b(e,"PBAS",2);if(t)return y(t.payload);let n=b(e,"PBAS",1);if(n)return y(n.payload)}function Q(e){let t=b(e,"PCON",2)??b(e,"PCON",1);if(!t)return;let n=new Array(t.elementCount);for(let r=0;r<t.elementCount;r++)n[r]=t.payload[r];return n}function X(e){let t=b(e,"PLOC",2)??b(e,"PLOC",1);if(!t)return;if(t.elementSize!==2)throw new Error(`PLOC: expected elementSize=2, got ${t.elementSize}`);let n=p(t.payload),r=new Array(t.elementCount);for(let a=0;a<t.elementCount;a++)r[a]=n.getUint16(a*2,false);return r}function ee(e){let t=b(e,"SPAC",1);if(!t||t.elementSize!==4||t.payload.byteLength<4)return;let n=p(t.payload).getFloat32(0,false);return Number.isFinite(n)&&n>0?n:void 0}function ie(e,t){v(e,"PBAS",2,x(t),{elementType:2,elementSize:1,elementCount:t.length});}function oe(e,t){let n=new Uint8Array(t.length);for(let r=0;r<t.length;r++)n[r]=Math.max(0,Math.min(255,Math.round(t[r])));v(e,"PCON",2,n,{elementType:2,elementSize:1,elementCount:t.length});}function se(e,t){let n=new Uint8Array(t.length*2),r=p(n);for(let a=0;a<t.length;a++)r.setUint16(a*2,Math.max(0,Math.min(65535,t[a])),false);v(e,"PLOC",2,n,{elementType:4,elementSize:2,elementCount:t.length});}function le(e,t,n){let r=new Uint8Array(4);p(r).setFloat32(0,t,false),v(e,"SPAC",1,r,{elementType:7,elementSize:4,elementCount:1});let a=x(n),i=Math.min(255,a.length),c=new Uint8Array(1+i);c[0]=i,c.set(a.subarray(0,i),1),v(e,"SPAC",2,c,{elementType:18,elementSize:1,elementCount:1+i});let m=new Uint8Array(4);p(m).setFloat32(0,t,false),v(e,"SPAC",3,m,{elementType:7,elementSize:4,elementCount:1});}function ue(e){return e.length<2?0:(e[e.length-1]-e[0])/(e.length-1)}function me(e){if(!I(e))for(let t=1;t<=4;t++){let n=b(e,"DATA",t);if(!n)continue;let r=8+t;b(e,"DATA",r)||e.entries.push({tagName:"DATA",tagNumber:r,elementType:n.elementType,elementSize:n.elementSize,elementCount:n.elementCount,payload:new Uint8Array(n.payload),dataHandle:0});}}var B={1:"byte",2:"char",3:"word",4:"short",5:"long",7:"float",8:"double",10:"date",11:"time",12:"thumb",13:"bool",18:"pString",19:"cString",1023:"tdir"};function E(e){return B[e]??(e>=1024?`user${e}`:`type${e}`)}function M(e,t=10){return e.length<=t?`[${e.join(", ")}]`:`[${e.slice(0,t).join(", ")}, \u2026 +${e.length-t}] (n=${e.length})`}function O(e,t=80){return e.length>t?e.slice(0,t)+"\u2026":e}function D(e){let t=e.subarray(0,16),n=Array.from(t,r=>r.toString(16).padStart(2,"0")).join(" ");return `${e.byteLength}B [${n}${e.byteLength>16?" \u2026":""}]`}function R(e,t,n){let r=p(n);switch(e){case 1:return {kind:"numbers",value:Array.from(n.subarray(0,t))};case 2:case 19:return {kind:"string",value:y(n.subarray(0,t)).replace(/\0+$/g,"")};case 3:{let a=[];for(let i=0;i<t;i++)a.push(r.getUint16(i*2,false));return a.length===1?{kind:"number",value:a[0]}:{kind:"numbers",value:a}}case 4:{let a=[];for(let i=0;i<t;i++)a.push(r.getInt16(i*2,false));return a.length===1?{kind:"number",value:a[0]}:{kind:"numbers",value:a}}case 5:{let a=[];for(let i=0;i<t;i++)a.push(r.getInt32(i*4,false));return a.length===1?{kind:"number",value:a[0]}:{kind:"numbers",value:a}}case 7:{let a=[];for(let i=0;i<t;i++)a.push(r.getFloat32(i*4,false));return a.length===1?{kind:"number",value:a[0]}:{kind:"numbers",value:a}}case 8:{let a=[];for(let i=0;i<t;i++)a.push(r.getFloat64(i*8,false));return a.length===1?{kind:"number",value:a[0]}:{kind:"numbers",value:a}}case 10:{let a=r.getInt16(0,false),i=r.getUint8(2),c=r.getUint8(3);return {kind:"date",value:{year:a,month:i,day:c}}}case 11:return {kind:"time",value:{hour:r.getUint8(0),minute:r.getUint8(1),second:r.getUint8(2),hsec:r.getUint8(3)}};case 13:{let a=[];for(let i=0;i<t;i++)a.push(r.getUint8(i)!==0);return {kind:"bools",value:a}}case 18:{let a=r.getUint8(0);return {kind:"string",value:y(C(n,1,Math.min(a,Math.max(0,t-1))))}}default:return {kind:"unknown",value:n}}}function G(e,t){switch(e.kind){case "number":return String(e.value);case "numbers":return M(e.value);case "string":return O(JSON.stringify(e.value));case "bools":return e.value.length===1?String(e.value[0]):`[${e.value.join(", ")}]`;case "date":{let{year:n,month:r,day:a}=e.value;return `${n}-${String(r).padStart(2,"0")}-${String(a).padStart(2,"0")}`}case "time":{let{hour:n,minute:r,second:a}=e.value;return `${String(n).padStart(2,"0")}:${String(r).padStart(2,"0")}:${String(a).padStart(2,"0")}`}case "bytes":return `${E(t)} ${D(e.value)}`;case "unknown":return `${E(t)} ${D(e.value)}`}}function be(e,t=""){let n=e instanceof Uint8Array?e:new Uint8Array(e),r=U(n),a=r.entries.map(u=>{let s=R(u.elementType,u.elementCount,u.payload);return {tag:u.tagName,tagNumber:u.tagNumber,elementType:u.elementType,elementTypeName:E(u.elementType),elementSize:u.elementSize,elementCount:u.elementCount,dataSize:u.elementCount*u.elementSize,dataOffset:0,inline:u.elementCount*u.elementSize<=4,decoded:s,preview:G(s,u.elementType)}}),i=T(r);/^[ACGT]{4}$/.test(i)||(i="GATC");let c={},m={A:[],C:[],G:[],T:[]},d={A:[],C:[],G:[],T:[]},o={comments:[]},g={},h={},N={},$=u=>i[u];for(let u=0;u<r.entries.length;u++){let s=r.entries[u],l=a[u].decoded;if(s.tagName==="DATA"&&l.kind==="numbers")c[s.tagNumber]=l.value,s.tagNumber>=1&&s.tagNumber<=4?m[$(s.tagNumber-1)]=l.value:s.tagNumber>=9&&s.tagNumber<=12&&(d[$(s.tagNumber-9)]=l.value);else if(s.tagName==="PBAS"&&l.kind==="string")g[s.tagNumber]=l.value;else if(s.tagName==="PCON")l.kind==="numbers"?h[s.tagNumber]=l.value:l.kind==="string"&&(h[s.tagNumber]=Array.from(l.value,f=>f.charCodeAt(0)));else if(s.tagName==="PLOC"&&l.kind==="numbers")N[s.tagNumber]=l.value.map(f=>f<0?f+65536:f);else if(s.tagName==="SPAC")if(s.elementSize===4&&s.payload.byteLength>=4){let f=p(s.payload).getFloat32(0,false);Number.isFinite(f)&&f>0&&(o.samplingRate=f);}else l.kind==="number"&&(o.samplingRate=l.value);else if(s.tagName==="SMPL"&&l.kind==="string")o.sampleName=l.value;else if(s.tagName==="LANE"&&l.kind==="number")o.laneNumber=l.value;else if(s.tagName==="TUBE"&&l.kind==="string")o.tube=l.value;else if(s.tagName==="MCHN"&&l.kind==="string")o.machineName=l.value;else if(s.tagName==="MODL"&&l.kind==="string")o.machineModel=l.value;else if(s.tagName==="RUND"&&l.kind==="date"){let f=l.value;o.runDate=`${f.year}-${String(f.month).padStart(2,"0")}-${String(f.day).padStart(2,"0")}`;}else if(s.tagName==="RUNT"&&l.kind==="time"){let f=l.value;o.runTime=`${String(f.hour).padStart(2,"0")}:${String(f.minute).padStart(2,"0")}:${String(f.second).padStart(2,"0")}`;}else s.tagName==="CMNT"&&l.kind==="string"&&o.comments.push(l.value);}let S,A=g[2]?2:g[1]?1:Object.keys(g).map(Number)[0];if(A!==void 0&&g[A]){let u=g[A].toUpperCase(),s=h[A]??[];if(s.length===0){for(let f of Object.keys(h).map(Number))if(h[f].length===u.length){s=h[f];break}}let l=N[A]??[];if(l.length===0){for(let f of Object.keys(N).map(Number))if(N[f].length===u.length){l=N[f];break}}S={sequence:u,confidences:s,positions:l,pbasVersion:A};}if(!Number.isFinite(o.samplingRate)||(o.samplingRate??0)<=0){let u=S?.positions,s=Math.max(d.A.length,d.C.length,d.G.length,d.T.length);u&&u.length>1?o.samplingRate=(u[u.length-1]-u[0])/(u.length-1):S&&S.sequence.length>0&&s>0&&(o.samplingRate=s/S.sequence.length);}let L={baseOrder:i,dataChannels:c,data1To4:m,data9To12:d};return {fileName:t,fileSize:n.byteLength,abifVersion:r.version,macBinaryOffset:r.macBinaryOffset,dirEntryCount:r.entries.length,metadata:o,chromatogram:L,baseCalls:S,entries:a}}function he(e){return e.A.length>0||e.C.length>0||e.G.length>0||e.T.length>0}function pe(e){return Math.max(e.A.length,e.C.length,e.G.length,e.T.length)}exports.ENTRY_SIZE=w;exports.HEADER_SIZE=F;exports.averagePeakSpacing=ue;exports.channelMaxLength=pe;exports.ensureRawDataChannels=me;exports.findEntries=_;exports.findEntry=b;exports.getConfidences=Q;exports.getDataChannel=Z;exports.getFwo=T;exports.getPositions=X;exports.getRawChannelMap=J;exports.getSamplingRate=ee;exports.getSequence=K;exports.hasProcessedTraces=I;exports.hasSignals=he;exports.parseAbif=be;exports.readAbif=U;exports.setAveragePeakSpacing=le;exports.setConfidences=oe;exports.setPositions=se;exports.setSequence=ie;exports.tagNameFromInt32=P;exports.upsertEntry=v;exports.writeAbif=H;//# sourceMappingURL=index.js.map
1
+ 'use strict';function v(e){let t="";for(let n=0;n<e.length;n++)t+=String.fromCharCode(e[n]);return t}function z(e){let t=new Uint8Array(e.length);for(let n=0;n<e.length;n++)t[n]=e.charCodeAt(n)&255;return t}function w(e){return new DataView(e.buffer,e.byteOffset,e.byteLength)}function N(e,t,n){return e.subarray(t,t+n)}function G(e){return String.fromCharCode(e>>>24&255,e>>>16&255,e>>>8&255,e&255)}var $=128,x=28;function O(e){if(e.byteLength<$)throw new Error(`ABIF too small: ${e.byteLength} bytes`);let t=0;if(v(N(e,0,4))!=="ABIF")if(e.byteLength>=132&&v(N(e,128,4))==="ABIF")t=128;else throw new Error('Not an ABIF file: missing "ABIF" magic.');let n=t;if(e.byteLength-n<$)throw new Error(`ABIF too small: ${e.byteLength-n} bytes after offset ${n}`);let r=w(e),a=r.getInt16(n+4,false),i=B(e,r,n+6,n);if(i.tagName!=="tdir")throw new Error(`Expected "tdir" header entry, got "${i.tagName}"`);if(i.elementSize!==x)throw new Error(`Expected dir element size ${x}, got ${i.elementSize}`);let c=i.raw?.elementCount??i.elementCount,u=Math.max(0,c),d=r.getInt32(n+6+20,false);if(d<0||d+u*x>e.byteLength-n)throw new Error(`Directory out of bounds: offset=${d}, entries=${u}, file=${e.byteLength-n}`);let s=[];for(let E=0;E<u;E++)s.push(B(e,r,n+d+E*x,n));let g=i.raw?.dataSize??u*x,S=n+d+u*x,b=Math.min(Math.max(0,g-u*x),Math.max(0,e.byteLength-S)),h={entryCount:u,rawEntryCount:c,elementType:i.elementType,tagNumber:i.tagNumber,entrySize:i.elementSize,dataSize:g,dataOffset:d,dataOffsetBytes:i.raw?.dataOffsetBytes??new Uint8Array(4),dataHandle:i.dataHandle,paddingBytes:new Uint8Array(N(e,S,b))},y=new Uint8Array(N(e,n+6+x,$-6-x)),p=t===128?new Uint8Array(N(e,0,128)):void 0,C=H(e,n,d,g,s,t);return {version:a,tdir:h,entries:s,macBinaryOffset:t,macBinaryHeader:p,headerReserved:y,unreferencedRanges:C}}function H(e,t,n,r,a,i){let c=e.byteLength,u=[];i===128&&u.push([0,128]),u.push([t,t+$]);let d=Math.max(a.length*x,Math.max(0,r));u.push([t+n,t+n+d]);for(let h of a)if(h.raw&&!h.raw.inline){let y=t+h.raw.dataOffset;u.push([y,y+h.payload.byteLength]);}let s=u.map(([h,y])=>[Math.max(0,Math.min(h,c)),Math.max(0,Math.min(y,c))]).filter(([h,y])=>y>h).sort((h,y)=>h[0]-y[0]),g=[];for(let[h,y]of s){let p=g[g.length-1];p&&h<=p[1]?p[1]=Math.max(p[1],y):g.push([h,y]);}let S=[],b=0;for(let[h,y]of g)h>b&&S.push({offset:b,bytes:new Uint8Array(N(e,b,h-b))}),b=Math.max(b,y);return b<c&&S.push({offset:b,bytes:new Uint8Array(N(e,b,c-b))}),S}function B(e,t,n,r){let a=v(N(e,n,4)),i=t.getInt32(n+4,false),c=t.getInt16(n+8,false),u=t.getInt16(n+10,false),d=t.getInt32(n+12,false),s=t.getInt32(n+16,false),g=t.getInt32(n+24,false),S=Math.max(0,d*Math.max(0,u)),b=s>=0?s:S,h=s>=0?s<=4:S<=4,y=Math.max(0,u>0?Math.min(d,Math.floor(b/u)):Math.min(d,b)),p,C;if(h)C=-1,p=new Uint8Array(N(e,n+20,Math.min(4,b)));else {C=t.getInt32(n+20,false);let o=r+C;if(C<0||o+b>e.byteLength)throw new Error(`Entry ${a}${i}: payload out of bounds (offset=${C}, size=${b})`);p=new Uint8Array(N(e,o,b));}let E=new Uint8Array(N(e,n+20,4));return {tagName:a,tagNumber:i,elementType:c,elementSize:u,elementCount:y,payload:p,dataHandle:g,raw:{elementCount:d,dataSize:s,dataOffset:C,inline:h,dataOffsetBytes:E}}}function J(e){let t=e.entries.length,n=$,r=t*x,a=n+r,i=new Array(t);for(let s=0;s<t;s++){let g=e.entries[s].payload.byteLength;g>4?(i[s]=a,a+=g):i[s]=-1;}let c=a,u=new Uint8Array(c),d=w(u);u.set(z("ABIF"),0),d.setInt16(4,e.version,false),I({tagName:"tdir",tagNumber:1,elementType:1023,elementSize:x,elementCount:t,payload:new Uint8Array(0),dataHandle:0},u,d,6,-1,r,n);for(let s=0;s<t;s++){let g=e.entries[s];I(g,u,d,n+s*x,i[s]);}for(let s=0;s<t;s++){let g=i[s];g>=0&&u.set(e.entries[s].payload,g);}return u}function I(e,t,n,r,a,i,c){if(e.tagName.length!==4)throw new Error(`tagName must be 4 chars: "${e.tagName}"`);t.set(z(e.tagName),r),n.setInt32(r+4,e.tagNumber,false),n.setInt16(r+8,e.elementType,false),n.setInt16(r+10,e.elementSize,false),n.setInt32(r+12,e.elementCount,false);let u=i??e.payload.byteLength;if(n.setInt32(r+16,u,false),c!==void 0)n.setInt32(r+20,c,false);else if(u<=4)t.fill(0,r+20,r+24),t.set(e.payload.subarray(0,u),r+20);else {if(a<0)throw new Error(`External payload requires offset for ${e.tagName}${e.tagNumber}`);n.setInt32(r+20,a,false);}n.setInt32(r+24,e.dataHandle,false);}function A(e,t,n){return e.entries.find(r=>r.tagName===t&&r.tagNumber===n)}function K(e,t){return e.entries.filter(n=>n.tagName===t)}function T(e,t,n,r,a){if(r.byteLength!==a.elementCount*a.elementSize)throw new Error(`upsertEntry ${t}${n}: payload length ${r.byteLength} != count*size`);let i=A(e,t,n);i?(i.elementType=a.elementType,i.elementSize=a.elementSize,i.elementCount=a.elementCount,i.payload=r):e.entries.push({tagName:t,tagNumber:n,elementType:a.elementType,elementSize:a.elementSize,elementCount:a.elementCount,payload:r,dataHandle:0});}function te(e,t){let n=A(e,"DATA",t);if(!n)return;if(n.elementSize!==2)throw new Error(`DATA${t}: expected elementSize=2, got ${n.elementSize}`);let r=new Int16Array(n.elementCount),a=w(n.payload);for(let i=0;i<n.elementCount;i++)r[i]=a.getInt16(i*2,false);return r}function k(e){let t=A(e,"FWO_",1);return t?v(t.payload.subarray(0,t.elementCount)):"GATC"}function L(e){return /^[ACGT]{4}$/.test(e)&&new Set(e).size===4}function R(e){return !!(A(e,"DATA",9)&&A(e,"DATA",10)&&A(e,"DATA",11)&&A(e,"DATA",12))}function ne(e){return e>=5&&e<=8?"telemetry":e>=1&&e<=4||e>=9&&e<=12||e===105||e===205?"trace":"other"}function re(e){let t=A(e,"RevC",1);if(!t)return;let n=w(t.payload);return t.payload.byteLength>=2?n.getInt16(0,false)!==0:t.payload.some(r=>r!==0)}function ae(e){let t=k(e);if(!L(t))throw new Error(`FWO_ is not a permutation of A/C/G/T: "${t}"`);return {[t[0]]:1,[t[1]]:2,[t[2]]:3,[t[3]]:4}}function ie(e){let t=a=>v(a.payload.subarray(0,a.elementCount)).replace(/\0+$/g,""),n=A(e,"PBAS",2);if(n)return t(n);let r=A(e,"PBAS",1);if(r)return t(r)}function oe(e){let t=A(e,"PCON",2)??A(e,"PCON",1);if(!t)return;let n=new Array(t.elementCount);for(let r=0;r<t.elementCount;r++)n[r]=t.payload[r];return n}function se(e){let t=A(e,"PLOC",2)??A(e,"PLOC",1);if(!t)return;if(t.elementSize!==2)throw new Error(`PLOC: expected elementSize=2, got ${t.elementSize}`);let n=w(t.payload),r=new Array(t.elementCount);for(let a=0;a<t.elementCount;a++)r[a]=n.getUint16(a*2,false);return r}function le(e){let t=A(e,"SPAC",1);if(!t||t.elementSize!==4||t.payload.byteLength<4)return;let n=w(t.payload).getFloat32(0,false);return Number.isFinite(n)&&n>0?n:void 0}function ge(e,t){T(e,"PBAS",2,z(t),{elementType:2,elementSize:1,elementCount:t.length});}function de(e,t){let n=new Uint8Array(t.length);for(let r=0;r<t.length;r++)n[r]=Math.max(0,Math.min(255,Math.round(t[r])));T(e,"PCON",2,n,{elementType:2,elementSize:1,elementCount:t.length});}function be(e,t){let n=new Uint8Array(t.length*2),r=w(n);for(let a=0;a<t.length;a++)r.setUint16(a*2,Math.max(0,Math.min(65535,t[a])),false);T(e,"PLOC",2,n,{elementType:4,elementSize:2,elementCount:t.length});}function he(e,t,n){let r=new Uint8Array(4);w(r).setFloat32(0,t,false),T(e,"SPAC",1,r,{elementType:7,elementSize:4,elementCount:1});let a=z(n),i=Math.min(255,a.length),c=new Uint8Array(1+i);c[0]=i,c.set(a.subarray(0,i),1),T(e,"SPAC",2,c,{elementType:18,elementSize:1,elementCount:1+i});let u=new Uint8Array(4);w(u).setFloat32(0,t,false),T(e,"SPAC",3,u,{elementType:7,elementSize:4,elementCount:1});}function ye(e){return e.length<2?0:(e[e.length-1]-e[0])/(e.length-1)}function Ae(e){if(!R(e))for(let t=1;t<=4;t++){let n=A(e,"DATA",t);if(!n)continue;let r=8+t;A(e,"DATA",r)||e.entries.push({tagName:"DATA",tagNumber:r,elementType:n.elementType,elementSize:n.elementSize,elementCount:n.elementCount,payload:new Uint8Array(n.payload),dataHandle:0});}}var j={1:"byte",2:"char",3:"word",4:"short",5:"long",7:"float",8:"double",10:"date",11:"time",12:"thumb",13:"bool",18:"pString",19:"cString",1023:"tdir"};function M(e){return j[e]??(e>=1024?`user${e}`:`type${e}`)}function _(e,t=10){return e.length<=t?`[${e.join(", ")}]`:`[${e.slice(0,t).join(", ")}, \u2026 +${e.length-t}] (n=${e.length})`}function q(e,t=80){return e.length>t?e.slice(0,t)+"\u2026":e}function P(e){let t=e.subarray(0,16),n=Array.from(t,r=>r.toString(16).padStart(2,"0")).join(" ");return `${e.byteLength}B [${n}${e.byteLength>16?" \u2026":""}]`}function D(e){if(e.kind==="numbers")return e.value;if(e.kind==="number")return [e.value]}function U(e,t,n,r){let a=Math.max(0,Math.min(t,Math.floor(e/n))),i=[];for(let c=0;c<a;c++)i.push(r(c*n));return i.length===1?{kind:"number",value:i[0]}:{kind:"numbers",value:i}}function V(e,t,n){let r=w(n);switch(e){case 1:return {kind:"numbers",value:Array.from(n.subarray(0,t))};case 2:case 19:return {kind:"string",value:v(n.subarray(0,t)).replace(/\0+$/g,"")};case 3:return U(n.byteLength,t,2,a=>r.getUint16(a,false));case 4:return U(n.byteLength,t,2,a=>r.getInt16(a,false));case 5:return U(n.byteLength,t,4,a=>r.getInt32(a,false));case 7:return U(n.byteLength,t,4,a=>r.getFloat32(a,false));case 8:return U(n.byteLength,t,8,a=>r.getFloat64(a,false));case 10:{if(n.byteLength<4)return {kind:"unknown",value:n};let a=r.getInt16(0,false),i=r.getUint8(2),c=r.getUint8(3);return {kind:"date",value:{year:a,month:i,day:c}}}case 11:return n.byteLength<4?{kind:"unknown",value:n}:{kind:"time",value:{hour:r.getUint8(0),minute:r.getUint8(1),second:r.getUint8(2),hsec:r.getUint8(3)}};case 13:{let a=[];for(let i=0;i<Math.min(t,n.byteLength);i++)a.push(r.getUint8(i)!==0);return {kind:"bools",value:a}}case 18:{if(n.byteLength<1)return {kind:"string",value:""};let a=r.getUint8(0);return {kind:"string",value:v(N(n,1,Math.min(a,Math.max(0,t-1))))}}default:return {kind:"unknown",value:n}}}function W(e,t){switch(e.kind){case "number":return String(e.value);case "numbers":return _(e.value);case "string":return q(JSON.stringify(e.value));case "bools":return e.value.length===1?String(e.value[0]):`[${e.value.join(", ")}]`;case "date":{let{year:n,month:r,day:a}=e.value;return `${n}-${String(r).padStart(2,"0")}-${String(a).padStart(2,"0")}`}case "time":{let{hour:n,minute:r,second:a}=e.value;return `${String(n).padStart(2,"0")}:${String(r).padStart(2,"0")}:${String(a).padStart(2,"0")}`}case "bytes":return `${M(t)} ${P(e.value)}`;case "unknown":return `${M(t)} ${P(e.value)}`}}function Ne(e,t=""){let n=e instanceof Uint8Array?e:new Uint8Array(e),r=O(n),a=r.entries.map(o=>{let l=o.tagName==="PCON"&&o.elementSize===1?V(1,o.elementCount,o.payload):V(o.elementType,o.elementCount,o.payload);return {tag:o.tagName,tagNumber:o.tagNumber,elementType:o.elementType,elementTypeName:M(o.elementType),elementSize:o.elementSize,elementCount:o.elementCount,rawElementCount:o.raw?.elementCount??o.elementCount,dataSize:o.raw?.dataSize??o.elementCount*o.elementSize,dataOffset:o.raw?.dataOffset??-1,inline:o.raw?.inline??o.elementCount*o.elementSize<=4,decoded:l,preview:W(l,o.elementType)}}),i=k(r);L(i)||(i="GATC");let c={},u={A:[],C:[],G:[],T:[]},d={A:[],C:[],G:[],T:[]},s={comments:[]},g={},S={},b={},h=o=>i[o];for(let o=0;o<r.entries.length;o++){let l=r.entries[o],f=a[o].decoded;if(l.tagName==="DATA"){let m=D(f);m&&(c[l.tagNumber]=m,l.tagNumber>=1&&l.tagNumber<=4?u[h(l.tagNumber-1)]=m:l.tagNumber>=9&&l.tagNumber<=12&&(d[h(l.tagNumber-9)]=m));}else if(l.tagName==="PBAS"&&f.kind==="string")g[l.tagNumber]=f.value;else if(l.tagName==="PCON"){let m=l.elementSize===1?Array.from(l.payload.subarray(0,l.elementCount)):D(f);m&&(S[l.tagNumber]=m);}else if(l.tagName==="PLOC"){let m=D(f);m&&(b[l.tagNumber]=m.map(F=>F<0?F+65536:F));}else if(l.tagName==="SPAC")if(l.elementSize===4&&l.payload.byteLength>=4){let m=w(l.payload).getFloat32(0,false);Number.isFinite(m)&&m>0&&(s.samplingRate=m);}else f.kind==="number"&&(s.samplingRate=f.value);else if(l.tagName==="SMPL"&&f.kind==="string")s.sampleName=f.value;else if(l.tagName==="LANE"&&f.kind==="number")s.laneNumber=f.value;else if(l.tagName==="TUBE"&&f.kind==="string")s.tube=f.value;else if(l.tagName==="MCHN"&&f.kind==="string")s.machineName=f.value;else if(l.tagName==="MODL"&&f.kind==="string")s.machineModel=f.value;else if(l.tagName==="RUND"&&f.kind==="date"){let m=f.value;s.runDate=`${m.year}-${String(m.month).padStart(2,"0")}-${String(m.day).padStart(2,"0")}`;}else if(l.tagName==="RUNT"&&f.kind==="time"){let m=f.value;s.runTime=`${String(m.hour).padStart(2,"0")}:${String(m.minute).padStart(2,"0")}:${String(m.second).padStart(2,"0")}`;}else l.tagName==="CMNT"&&f.kind==="string"?s.comments.push(f.value):l.tagName==="RevC"&&l.tagNumber===1&&(f.kind==="number"?s.reverseComplemented=f.value!==0:f.kind==="numbers"&&(s.reverseComplemented=f.value.some(m=>m!==0)));}let y=Object.keys(g).map(Number).sort((o,l)=>o-l).map(o=>({version:o,role:o===1?"edited":o===2?"called":"unknown",sequence:g[o],confidences:S[o]??[],positions:b[o]??[]})),p,C=2 in g?2:1 in g?1:Object.keys(g).map(Number)[0];if(C!==void 0&&g[C]!==void 0){let o=g[C].toUpperCase(),l=S[C]??[];l.length!==o.length&&(l=Object.keys(S).map(Number).map(m=>S[m]).find(m=>m.length===o.length)??[]);let f=b[C]??[];f.length!==o.length&&(f=Object.keys(b).map(Number).map(m=>b[m]).find(m=>m.length===o.length)??[]),p={sequence:o,confidences:l,positions:f,pbasVersion:C};}if(!Number.isFinite(s.samplingRate)||(s.samplingRate??0)<=0){let o=p?.positions,l=Math.max(d.A.length,d.C.length,d.G.length,d.T.length);o&&o.length>1?s.samplingRate=(o[o.length-1]-o[0])/(o.length-1):p&&p.sequence.length>0&&l>0&&(s.samplingRate=l/p.sequence.length);}let E={baseOrder:i,dataChannels:c,data1To4:u,data9To12:d};return {fileName:t,fileSize:n.byteLength,abifVersion:r.version,macBinaryOffset:r.macBinaryOffset,dirEntryCount:r.entries.length,metadata:s,chromatogram:E,baseCalls:p,baseCallVariants:y,entries:a}}function xe(e){return e.A.length>0||e.C.length>0||e.G.length>0||e.T.length>0}function ve(e){return Math.max(e.A.length,e.C.length,e.G.length,e.T.length)}exports.ENTRY_SIZE=x;exports.HEADER_SIZE=$;exports.averagePeakSpacing=ye;exports.channelMaxLength=ve;exports.dataChannelRole=ne;exports.ensureRawDataChannels=Ae;exports.findEntries=K;exports.findEntry=A;exports.getChannelMap=ae;exports.getConfidences=oe;exports.getDataChannel=te;exports.getFwo=k;exports.getPositions=se;exports.getReverseComplemented=re;exports.getSamplingRate=le;exports.getSequence=ie;exports.hasData9To12Block=R;exports.hasSignals=xe;exports.isFwoPermutation=L;exports.parseAbif=Ne;exports.readAbif=O;exports.setAveragePeakSpacing=he;exports.setConfidences=de;exports.setPositions=be;exports.setSequence=ge;exports.tagNameFromInt32=G;exports.upsertEntry=T;exports.writeAbif=J;//# sourceMappingURL=index.js.map
2
2
  //# sourceMappingURL=index.js.map