@gmod/cram 5.0.6 → 6.0.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 (85) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/dist/cram-bundle.js +1 -1
  3. package/dist/cram-bundle.js.LICENSE.txt +0 -6
  4. package/dist/cramFile/codecs/byteArrayLength.js +8 -4
  5. package/dist/cramFile/codecs/byteArrayLength.js.map +1 -1
  6. package/dist/cramFile/codecs/external.d.ts +1 -4
  7. package/dist/cramFile/codecs/external.js +15 -20
  8. package/dist/cramFile/codecs/external.js.map +1 -1
  9. package/dist/cramFile/file.d.ts +2 -7
  10. package/dist/cramFile/file.js +22 -22
  11. package/dist/cramFile/file.js.map +1 -1
  12. package/dist/cramFile/slice/index.js +14 -5
  13. package/dist/cramFile/slice/index.js.map +1 -1
  14. package/dist/cramFile/util.js +4 -3
  15. package/dist/cramFile/util.js.map +1 -1
  16. package/dist/indexedCramFile.js +15 -4
  17. package/dist/indexedCramFile.js.map +1 -1
  18. package/dist/rans/d04.js +42 -16
  19. package/dist/rans/d04.js.map +1 -1
  20. package/dist/rans/d14.js +60 -23
  21. package/dist/rans/d14.js.map +1 -1
  22. package/dist/rans/decoding.js +3 -4
  23. package/dist/rans/decoding.js.map +1 -1
  24. package/dist/rans/frequencies.js +18 -24
  25. package/dist/rans/frequencies.js.map +1 -1
  26. package/dist/rans/index.js +9 -14
  27. package/dist/rans/index.js.map +1 -1
  28. package/dist/unzip.d.ts +1 -1
  29. package/dist/unzip.js +2 -36
  30. package/dist/unzip.js.map +1 -1
  31. package/dist/xz-decompress/wasm.d.ts +1 -0
  32. package/dist/xz-decompress/wasm.js +5 -0
  33. package/dist/xz-decompress/wasm.js.map +1 -0
  34. package/dist/xz-decompress/xz-decompress.d.ts +1 -0
  35. package/dist/xz-decompress/xz-decompress.js +123 -0
  36. package/dist/xz-decompress/xz-decompress.js.map +1 -0
  37. package/esm/cramFile/codecs/byteArrayLength.js +8 -4
  38. package/esm/cramFile/codecs/byteArrayLength.js.map +1 -1
  39. package/esm/cramFile/codecs/external.d.ts +1 -4
  40. package/esm/cramFile/codecs/external.js +15 -20
  41. package/esm/cramFile/codecs/external.js.map +1 -1
  42. package/esm/cramFile/file.d.ts +2 -7
  43. package/esm/cramFile/file.js +22 -22
  44. package/esm/cramFile/file.js.map +1 -1
  45. package/esm/cramFile/slice/index.js +14 -5
  46. package/esm/cramFile/slice/index.js.map +1 -1
  47. package/esm/cramFile/util.js +4 -3
  48. package/esm/cramFile/util.js.map +1 -1
  49. package/esm/indexedCramFile.js +15 -4
  50. package/esm/indexedCramFile.js.map +1 -1
  51. package/esm/rans/d04.js +42 -16
  52. package/esm/rans/d04.js.map +1 -1
  53. package/esm/rans/d14.js +60 -20
  54. package/esm/rans/d14.js.map +1 -1
  55. package/esm/rans/decoding.js +3 -4
  56. package/esm/rans/decoding.js.map +1 -1
  57. package/esm/rans/frequencies.js +18 -24
  58. package/esm/rans/frequencies.js.map +1 -1
  59. package/esm/rans/index.js +9 -14
  60. package/esm/rans/index.js.map +1 -1
  61. package/esm/unzip.d.ts +1 -1
  62. package/esm/unzip.js +2 -3
  63. package/esm/unzip.js.map +1 -1
  64. package/esm/xz-decompress/wasm.d.ts +1 -0
  65. package/esm/xz-decompress/wasm.js +2 -0
  66. package/esm/xz-decompress/wasm.js.map +1 -0
  67. package/esm/xz-decompress/xz-decompress.d.ts +1 -0
  68. package/esm/xz-decompress/xz-decompress.js +120 -0
  69. package/esm/xz-decompress/xz-decompress.js.map +1 -0
  70. package/package.json +6 -8
  71. package/src/cramFile/codecs/byteArrayLength.ts +8 -4
  72. package/src/cramFile/codecs/external.ts +19 -31
  73. package/src/cramFile/container/compressionScheme.ts +1 -1
  74. package/src/cramFile/file.ts +27 -27
  75. package/src/cramFile/slice/index.ts +18 -8
  76. package/src/cramFile/util.ts +4 -3
  77. package/src/indexedCramFile.ts +20 -7
  78. package/src/rans/d04.ts +48 -19
  79. package/src/rans/d14.ts +69 -23
  80. package/src/rans/decoding.ts +3 -4
  81. package/src/rans/frequencies.ts +18 -24
  82. package/src/rans/index.ts +10 -14
  83. package/src/unzip.ts +3 -4
  84. package/src/xz-decompress/wasm.ts +2 -0
  85. package/src/xz-decompress/xz-decompress.ts +155 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gmod/cram",
3
- "version": "5.0.6",
3
+ "version": "6.0.0",
4
4
  "description": "read CRAM files with pure Javascript",
5
5
  "license": "MIT",
6
6
  "repository": "GMOD/cram-js",
@@ -52,26 +52,24 @@
52
52
  "crc": "^4.3.2",
53
53
  "generic-filehandle2": "^2.0.12",
54
54
  "md5": "^2.2.1",
55
- "pako": "^1.0.4",
56
- "quick-lru": "^4.0.1",
57
- "xz-decompress": "^0.2.1"
55
+ "pako-esm2": "^1.0.15",
56
+ "quick-lru": "^4.0.1"
58
57
  },
59
58
  "devDependencies": {
60
59
  "@gmod/indexedfasta": "^4.0.6",
61
60
  "@types/md5": "^2.3.2",
62
- "@types/pako": "^1.0.3",
63
- "@vitest/coverage-v8": "^3.2.3",
61
+ "@vitest/coverage-v8": "^4.0.3",
64
62
  "buffer": "^6.0.3",
65
63
  "documentation": "^14.0.3",
66
64
  "eslint": "^9.29.0",
67
65
  "eslint-plugin-import": "^2.31.0",
68
- "eslint-plugin-unicorn": "^60.0.0",
66
+ "eslint-plugin-unicorn": "^62.0.0",
69
67
  "mock-fs": "^5.2.0",
70
68
  "prettier": "^3.2.5",
71
69
  "rimraf": "^6.0.1",
72
70
  "typescript": "^5.7.0",
73
71
  "typescript-eslint": "^8.34.0",
74
- "vitest": "^3.2.3",
72
+ "vitest": "^4.0.3",
75
73
  "webpack": "^5.99.9",
76
74
  "webpack-cli": "^6.0.1"
77
75
  },
@@ -35,11 +35,15 @@ export default class ByteArrayStopCodec extends CramCodec<
35
35
  const arrayLength =
36
36
  lengthCodec.decode(slice, coreDataBlock, blocksByContentId, cursors) || 0
37
37
 
38
- const dataCodec = this._getDataCodec()
39
38
  const data = new Uint8Array(arrayLength)
40
- for (let i = 0; i < arrayLength; i += 1) {
41
- data[i] =
42
- dataCodec.decode(slice, coreDataBlock, blocksByContentId, cursors) || 0
39
+ if (arrayLength > 0) {
40
+ const dataCodec = this._getDataCodec()
41
+ // Call decode directly on codec to avoid repeated lookups
42
+ for (let i = 0; i < arrayLength; i += 1) {
43
+ data[i] =
44
+ dataCodec.decode(slice, coreDataBlock, blocksByContentId, cursors) ||
45
+ 0
46
+ }
43
47
  }
44
48
 
45
49
  return data
@@ -1,4 +1,4 @@
1
- import CramCodec, { Cursor, Cursors } from './_base.ts'
1
+ import CramCodec, { Cursors } from './_base.ts'
2
2
  import { CramUnimplementedError } from '../../errors.ts'
3
3
  import { CramFileBlock } from '../file.ts'
4
4
  import CramSlice from '../slice/index.ts'
@@ -10,21 +10,12 @@ export default class ExternalCodec extends CramCodec<
10
10
  'int' | 'byte',
11
11
  ExternalCramEncoding['parameters']
12
12
  > {
13
- private readonly _decodeData: (
14
- contentBlock: CramFileBlock,
15
- cursor: Cursor,
16
- ) => number
17
-
18
13
  constructor(
19
14
  parameters: ExternalCramEncoding['parameters'],
20
15
  dataType: 'int' | 'byte',
21
16
  ) {
22
17
  super(parameters, dataType)
23
- if (this.dataType === 'int') {
24
- this._decodeData = this._decodeInt
25
- } else if (this.dataType === 'byte') {
26
- this._decodeData = this._decodeByte
27
- } else {
18
+ if (this.dataType !== 'int' && this.dataType !== 'byte') {
28
19
  throw new CramUnimplementedError(
29
20
  `${this.dataType} decoding not yet implemented by EXTERNAL codec`,
30
21
  )
@@ -39,29 +30,26 @@ export default class ExternalCodec extends CramCodec<
39
30
  ) {
40
31
  const { blockContentId } = this.parameters
41
32
  const contentBlock = blocksByContentId[blockContentId]
42
- return contentBlock
43
- ? this._decodeData(
44
- contentBlock,
45
- cursors.externalBlocks.getCursor(blockContentId),
46
- )
47
- : undefined
48
- }
33
+ if (!contentBlock) {
34
+ return undefined
35
+ }
49
36
 
50
- _decodeInt(contentBlock: CramFileBlock, cursor: Cursor) {
51
- const [result, bytesRead] = parseItf8(
52
- contentBlock.content,
53
- cursor.bytePosition,
54
- )
55
- cursor.bytePosition = cursor.bytePosition + bytesRead
56
- return result
57
- }
37
+ const cursor = cursors.externalBlocks.getCursor(blockContentId)
58
38
 
59
- _decodeByte(contentBlock: CramFileBlock, cursor: Cursor) {
60
- if (cursor.bytePosition >= contentBlock.content.length) {
61
- throw new CramBufferOverrunError(
62
- 'attempted to read beyond end of block. this file seems truncated.',
39
+ if (this.dataType === 'int') {
40
+ const [result, bytesRead] = parseItf8(
41
+ contentBlock.content,
42
+ cursor.bytePosition,
63
43
  )
44
+ cursor.bytePosition += bytesRead
45
+ return result
46
+ } else {
47
+ if (cursor.bytePosition >= contentBlock.content.length) {
48
+ throw new CramBufferOverrunError(
49
+ 'attempted to read beyond end of block. this file seems truncated.',
50
+ )
51
+ }
52
+ return contentBlock.content[cursor.bytePosition++]!
64
53
  }
65
- return contentBlock.content[cursor.bytePosition++]!
66
54
  }
67
55
  }
@@ -100,7 +100,7 @@ export default class CramContainerCompressionScheme {
100
100
  // interpret some of the preservation map tags for convenient use
101
101
  this.readNamesIncluded = content.preservation.RN
102
102
  this.APdelta = content.preservation.AP
103
-
103
+
104
104
  this.referenceRequired = !!content.preservation.RR
105
105
  this.tagIdsDictionary = content.preservation.TD
106
106
  this.substitutionMatrix = parseSubstitutionMatrix(content.preservation.SM)
@@ -1,12 +1,12 @@
1
1
  import crc32 from 'crc/calculators/crc32'
2
2
  import QuickLRU from 'quick-lru'
3
- import { XzReadableStream } from 'xz-decompress'
4
3
 
5
4
  import { CramMalformedError, CramUnimplementedError } from '../errors.ts'
6
5
  import * as htscodecs from '../htscodecs/index.ts'
7
6
  import { open } from '../io.ts'
8
7
  import ransuncompress from '../rans/index.ts'
9
8
  import { parseHeaderText } from '../sam.ts'
9
+ import { parseItem, tinyMemoize } from './util.ts'
10
10
  import { decode } from '../seek-bzip/index.ts'
11
11
  import { unzip } from '../unzip.ts'
12
12
  import CramContainer from './container/index.ts'
@@ -17,19 +17,10 @@ import {
17
17
  cramFileDefinition,
18
18
  getSectionParsers,
19
19
  } from './sectionParsers.ts'
20
- import { parseItem, tinyMemoize } from './util.ts'
20
+ import { xzDecompress } from '../xz-decompress/xz-decompress.ts'
21
21
 
22
22
  import type { GenericFilehandle } from 'generic-filehandle2'
23
23
 
24
- function bufferToStream(buf: Uint8Array) {
25
- return new ReadableStream({
26
- start(controller) {
27
- controller.enqueue(buf)
28
- controller.close()
29
- },
30
- })
31
- }
32
-
33
24
  // source: https://abdulapopoola.com/2019/01/20/check-endianness-with-javascript/
34
25
  function getEndianness() {
35
26
  const uInt32 = new Uint32Array([0x11223344])
@@ -80,6 +71,7 @@ export default class CramFile {
80
71
  }
81
72
  public featureCache: QuickLRU<string, Promise<CramRecord[]>>
82
73
  private header: string | undefined
74
+ private _sectionParsers?: ReturnType<typeof getSectionParsers>
83
75
 
84
76
  constructor(args: CramFileArgs) {
85
77
  this.file = open(args.url, args.path, args.filehandle)
@@ -148,9 +140,11 @@ export default class CramFile {
148
140
  }
149
141
 
150
142
  async getContainerById(containerNumber: number) {
151
- const { majorVersion } = await this.getDefinition()
152
- const sectionParsers = getSectionParsers(majorVersion)
153
- let position = sectionParsers.cramFileDefinition.maxLength
143
+ if (!this._sectionParsers) {
144
+ const { majorVersion } = await this.getDefinition()
145
+ this._sectionParsers = getSectionParsers(majorVersion)
146
+ }
147
+ let position = this._sectionParsers.cramFileDefinition.maxLength
154
148
 
155
149
  // skip with a series of reads to the proper container
156
150
  let currentContainer: CramContainer | undefined
@@ -208,11 +202,13 @@ export default class CramFile {
208
202
  * length check, relies on a try catch to read return an error to break
209
203
  */
210
204
  async containerCount(): Promise<number | undefined> {
211
- const { majorVersion } = await this.getDefinition()
212
- const sectionParsers = getSectionParsers(majorVersion)
205
+ if (!this._sectionParsers) {
206
+ const { majorVersion } = await this.getDefinition()
207
+ this._sectionParsers = getSectionParsers(majorVersion)
208
+ }
213
209
 
214
210
  let containerCount = 0
215
- let position = sectionParsers.cramFileDefinition.maxLength
211
+ let position = this._sectionParsers.cramFileDefinition.maxLength
216
212
  try {
217
213
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
218
214
  while (true) {
@@ -247,10 +243,14 @@ export default class CramFile {
247
243
  return new CramContainer(this, position)
248
244
  }
249
245
 
250
- async readBlockHeader(position: number) {
251
- const { majorVersion } = await this.getDefinition()
252
- const sectionParsers = getSectionParsers(majorVersion)
253
- const { cramBlockHeader } = sectionParsers
246
+ async readBlockHeader(
247
+ position: number,
248
+ ): Promise<BlockHeader & { _endPosition: number; _size: number }> {
249
+ if (!this._sectionParsers) {
250
+ const { majorVersion } = await this.getDefinition()
251
+ this._sectionParsers = getSectionParsers(majorVersion)
252
+ }
253
+ const { cramBlockHeader } = this._sectionParsers
254
254
 
255
255
  const buffer = await this.file.read(cramBlockHeader.maxLength, position)
256
256
  return parseItem(buffer, cramBlockHeader.parser, 0, position)
@@ -267,7 +267,7 @@ export default class CramFile {
267
267
  position: number,
268
268
  size = section.maxLength,
269
269
  preReadBuffer?: Uint8Array,
270
- ) {
270
+ ): Promise<T & { _endPosition: number; _size: number }> {
271
271
  const buffer = preReadBuffer ?? (await this.file.read(size, position))
272
272
  const data = parseItem(buffer, section.parser, 0, position)
273
273
  if (data._size !== size) {
@@ -289,10 +289,7 @@ export default class CramFile {
289
289
  } else if (compressionMethod === 'bzip2') {
290
290
  return decode(inputBuffer)
291
291
  } else if (compressionMethod === 'lzma') {
292
- const decompressedResponse = new Response(
293
- new XzReadableStream(bufferToStream(inputBuffer)),
294
- )
295
- return new Uint8Array(await decompressedResponse.arrayBuffer())
292
+ return xzDecompress(inputBuffer)
296
293
  } else if (compressionMethod === 'rans') {
297
294
  const outputBuffer = new Uint8Array(uncompressedSize)
298
295
  ransuncompress(inputBuffer, outputBuffer)
@@ -332,7 +329,10 @@ export default class CramFile {
332
329
 
333
330
  async readBlock(position: number) {
334
331
  const { majorVersion } = await this.getDefinition()
335
- const sectionParsers = getSectionParsers(majorVersion)
332
+ if (!this._sectionParsers) {
333
+ this._sectionParsers = getSectionParsers(majorVersion)
334
+ }
335
+ const sectionParsers = this._sectionParsers
336
336
  const blockHeader = await this.readBlockHeader(position)
337
337
  const blockContentPosition = blockHeader._endPosition
338
338
 
@@ -392,16 +392,23 @@ export default class CramSlice {
392
392
  },
393
393
  }
394
394
 
395
+ // Pre-resolve all codecs to avoid repeated lookups
396
+ const codecCache = new Map<DataSeriesEncodingKey, any>()
397
+
395
398
  const decodeDataSeries: DataSeriesDecoder = <
396
399
  T extends DataSeriesEncodingKey,
397
400
  >(
398
401
  dataSeriesName: T,
399
402
  ): DataTypeMapping[DataSeriesTypes[T]] | undefined => {
400
- const codec = compressionScheme.getCodecForDataSeries(dataSeriesName)
401
- if (!codec) {
402
- throw new CramMalformedError(
403
- `no codec defined for ${dataSeriesName} data series`,
404
- )
403
+ let codec = codecCache.get(dataSeriesName)
404
+ if (codec === undefined) {
405
+ codec = compressionScheme.getCodecForDataSeries(dataSeriesName)
406
+ if (!codec) {
407
+ throw new CramMalformedError(
408
+ `no codec defined for ${dataSeriesName} data series`,
409
+ )
410
+ }
411
+ codecCache.set(dataSeriesName, codec)
405
412
  }
406
413
  return codec.decode(this, coreDataBlock, blocksByContentId, cursors)
407
414
  }
@@ -431,10 +438,13 @@ export default class CramSlice {
431
438
  })
432
439
  } catch (e) {
433
440
  if (e instanceof CramBufferOverrunError) {
434
- console.warn(
435
- 'read attempted beyond end of buffer, file seems truncated.',
441
+ const recordsDecoded = i
442
+ const recordsExpected = sliceHeader.parsedContent.numRecords
443
+ throw new CramMalformedError(
444
+ `Failed to decode all records in slice. Decoded ${recordsDecoded} of ${recordsExpected} expected records. ` +
445
+ `Buffer overrun suggests either: (1) file is truncated/corrupted, (2) compression scheme is incorrect, ` +
446
+ `or (3) there's a bug in the decoder. Original error: ${e.message}`,
436
447
  )
437
- break
438
448
  } else {
439
449
  throw e
440
450
  }
@@ -183,14 +183,15 @@ export function tinyMemoize(_class: any, methodName: any) {
183
183
  const method = _class.prototype[methodName]
184
184
  const memoAttrName = `_memo_${methodName}`
185
185
  _class.prototype[methodName] = function _tinyMemoized() {
186
- if (!(memoAttrName in this)) {
187
- const res = method.call(this)
186
+ let res = this[memoAttrName]
187
+ if (res === undefined) {
188
+ res = method.call(this)
188
189
  this[memoAttrName] = res
189
190
  Promise.resolve(res).catch(() => {
190
191
  delete this[memoAttrName]
191
192
  })
192
193
  }
193
- return this[memoAttrName]
194
+ return res
194
195
  }
195
196
  }
196
197
 
@@ -104,14 +104,27 @@ export default class IndexedCramFile {
104
104
  // fetch all the slices and parse the feature data
105
105
  const sliceResults = await Promise.all(
106
106
  slices.map(slice =>
107
- this.getRecordsInSlice(
108
- slice,
109
- feature =>
110
- feature.sequenceId === seq &&
107
+ this.getRecordsInSlice(slice, feature => {
108
+ // Check if feature belongs to this sequence
109
+ if (feature.sequenceId !== seq) {
110
+ return false
111
+ }
112
+
113
+ // For unmapped reads (lengthOnRef is undefined), they are placed at their
114
+ // mate's position. Include them if that position is within the range.
115
+ if (feature.lengthOnRef === undefined) {
116
+ return (
117
+ feature.alignmentStart >= start && feature.alignmentStart <= end
118
+ )
119
+ }
120
+
121
+ // For mapped reads, check if they overlap the requested range
122
+ // Use > instead of >= for start boundary to match samtools behavior
123
+ return (
111
124
  feature.alignmentStart <= end &&
112
- feature.lengthOnRef !== undefined &&
113
- feature.alignmentStart + feature.lengthOnRef - 1 >= start,
114
- ),
125
+ feature.alignmentStart + feature.lengthOnRef - 1 > start
126
+ )
127
+ }),
115
128
  ),
116
129
  )
117
130
 
package/src/rans/d04.ts CHANGED
@@ -3,6 +3,10 @@ import { CramMalformedError } from '../errors.ts'
3
3
  import { TF_SHIFT } from './constants.ts'
4
4
  import Decoding from './decoding.ts'
5
5
 
6
+ // Inline constants for performance
7
+ const RANS_BYTE_L = 8388608 // 1 << 23
8
+ const MASK = 4095 // (1 << 12) - 1
9
+
6
10
  export default function uncompress(
7
11
  /* ByteBuffer */ input,
8
12
  /* Decoding.AriDecoder */ D,
@@ -16,26 +20,51 @@ export default function uncompress(
16
20
 
17
21
  const /* int */ outputSize = out.remaining()
18
22
  const /* int */ outputEnd = outputSize & ~3
23
+ const D_R = D.R
24
+
19
25
  for (let i = 0; i < outputEnd; i += 4) {
20
- const /* byte */ c0 = D.R[Decoding.get(rans0, TF_SHIFT)]
21
- const /* byte */ c1 = D.R[Decoding.get(rans1, TF_SHIFT)]
22
- const /* byte */ c2 = D.R[Decoding.get(rans2, TF_SHIFT)]
23
- const /* byte */ c3 = D.R[Decoding.get(rans3, TF_SHIFT)]
24
-
25
- out.putAt(i, c0)
26
- out.putAt(i + 1, c1)
27
- out.putAt(i + 2, c2)
28
- out.putAt(i + 3, c3)
29
-
30
- rans0 = Decoding.advanceSymbolStep(rans0, syms[0xff & c0], TF_SHIFT)
31
- rans1 = Decoding.advanceSymbolStep(rans1, syms[0xff & c1], TF_SHIFT)
32
- rans2 = Decoding.advanceSymbolStep(rans2, syms[0xff & c2], TF_SHIFT)
33
- rans3 = Decoding.advanceSymbolStep(rans3, syms[0xff & c3], TF_SHIFT)
34
-
35
- rans0 = Decoding.renormalize(rans0, input)
36
- rans1 = Decoding.renormalize(rans1, input)
37
- rans2 = Decoding.renormalize(rans2, input)
38
- rans3 = Decoding.renormalize(rans3, input)
26
+ const /* byte */ c0 = D_R[rans0 & MASK]
27
+ const /* byte */ c1 = D_R[rans1 & MASK]
28
+ const /* byte */ c2 = D_R[rans2 & MASK]
29
+ const /* byte */ c3 = D_R[rans3 & MASK]
30
+
31
+ // Inline putAt to avoid function call overhead
32
+ out._buffer[i] = c0
33
+ out._buffer[i + 1] = c1
34
+ out._buffer[i + 2] = c2
35
+ out._buffer[i + 3] = c3
36
+
37
+ const sym0 = syms[0xff & c0]
38
+ const sym1 = syms[0xff & c1]
39
+ const sym2 = syms[0xff & c2]
40
+ const sym3 = syms[0xff & c3]
41
+
42
+ rans0 = sym0.freq * (rans0 >> TF_SHIFT) + (rans0 & MASK) - sym0.start
43
+ rans1 = sym1.freq * (rans1 >> TF_SHIFT) + (rans1 & MASK) - sym1.start
44
+ rans2 = sym2.freq * (rans2 >> TF_SHIFT) + (rans2 & MASK) - sym2.start
45
+ rans3 = sym3.freq * (rans3 >> TF_SHIFT) + (rans3 & MASK) - sym3.start
46
+
47
+ // Inline renormalize to avoid function call overhead
48
+ if (rans0 < RANS_BYTE_L) {
49
+ do {
50
+ rans0 = (rans0 << 8) | (0xff & input.get())
51
+ } while (rans0 < RANS_BYTE_L)
52
+ }
53
+ if (rans1 < RANS_BYTE_L) {
54
+ do {
55
+ rans1 = (rans1 << 8) | (0xff & input.get())
56
+ } while (rans1 < RANS_BYTE_L)
57
+ }
58
+ if (rans2 < RANS_BYTE_L) {
59
+ do {
60
+ rans2 = (rans2 << 8) | (0xff & input.get())
61
+ } while (rans2 < RANS_BYTE_L)
62
+ }
63
+ if (rans3 < RANS_BYTE_L) {
64
+ do {
65
+ rans3 = (rans3 << 8) | (0xff & input.get())
66
+ } while (rans3 < RANS_BYTE_L)
67
+ }
39
68
  }
40
69
 
41
70
  out.setPosition(outputEnd)
package/src/rans/d14.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  // @ts-nocheck
2
2
  import { TF_SHIFT } from './constants.ts'
3
- import Decoding from './decoding.ts'
3
+
4
+ // Inline constants for performance
5
+ const RANS_BYTE_L = 8388608 // 1 << 23
6
+ const MASK = 4095 // (1 << 12) - 1
4
7
 
5
8
  export default function uncompress(
6
9
  /* ByteBuffer */ input,
@@ -23,26 +26,59 @@ export default function uncompress(
23
26
  let /* int */ l1 = 0
24
27
  let /* int */ l2 = 0
25
28
  let /* int */ l7 = 0
29
+
26
30
  for (; i0 < isz4; i0 += 1, i1 += 1, i2 += 1, i7 += 1) {
27
- const /* int */ c0 = 0xff & D[l0].R[Decoding.get(rans0, TF_SHIFT)]
28
- const /* int */ c1 = 0xff & D[l1].R[Decoding.get(rans1, TF_SHIFT)]
29
- const /* int */ c2 = 0xff & D[l2].R[Decoding.get(rans2, TF_SHIFT)]
30
- const /* int */ c7 = 0xff & D[l7].R[Decoding.get(rans7, TF_SHIFT)]
31
-
32
- output.putAt(i0, c0)
33
- output.putAt(i1, c1)
34
- output.putAt(i2, c2)
35
- output.putAt(i7, c7)
36
-
37
- rans0 = Decoding.advanceSymbolStep(rans0, syms[l0][c0], TF_SHIFT)
38
- rans1 = Decoding.advanceSymbolStep(rans1, syms[l1][c1], TF_SHIFT)
39
- rans2 = Decoding.advanceSymbolStep(rans2, syms[l2][c2], TF_SHIFT)
40
- rans7 = Decoding.advanceSymbolStep(rans7, syms[l7][c7], TF_SHIFT)
41
-
42
- rans0 = Decoding.renormalize(rans0, input)
43
- rans1 = Decoding.renormalize(rans1, input)
44
- rans2 = Decoding.renormalize(rans2, input)
45
- rans7 = Decoding.renormalize(rans7, input)
31
+ const D_l0_R = D[l0].R
32
+ const D_l1_R = D[l1].R
33
+ const D_l2_R = D[l2].R
34
+ const D_l7_R = D[l7].R
35
+
36
+ const /* int */ c0 = 0xff & D_l0_R[rans0 & MASK]
37
+ const /* int */ c1 = 0xff & D_l1_R[rans1 & MASK]
38
+ const /* int */ c2 = 0xff & D_l2_R[rans2 & MASK]
39
+ const /* int */ c7 = 0xff & D_l7_R[rans7 & MASK]
40
+
41
+ // Inline putAt to avoid function call overhead
42
+ output._buffer[i0] = c0
43
+ output._buffer[i1] = c1
44
+ output._buffer[i2] = c2
45
+ output._buffer[i7] = c7
46
+
47
+ const sym_l0_c0 = syms[l0][c0]
48
+ const sym_l1_c1 = syms[l1][c1]
49
+ const sym_l2_c2 = syms[l2][c2]
50
+ const sym_l7_c7 = syms[l7][c7]
51
+
52
+ rans0 =
53
+ sym_l0_c0.freq * (rans0 >> TF_SHIFT) + (rans0 & MASK) - sym_l0_c0.start
54
+ rans1 =
55
+ sym_l1_c1.freq * (rans1 >> TF_SHIFT) + (rans1 & MASK) - sym_l1_c1.start
56
+ rans2 =
57
+ sym_l2_c2.freq * (rans2 >> TF_SHIFT) + (rans2 & MASK) - sym_l2_c2.start
58
+ rans7 =
59
+ sym_l7_c7.freq * (rans7 >> TF_SHIFT) + (rans7 & MASK) - sym_l7_c7.start
60
+
61
+ // Inline renormalize to avoid function call overhead
62
+ if (rans0 < RANS_BYTE_L) {
63
+ do {
64
+ rans0 = (rans0 << 8) | (0xff & input.get())
65
+ } while (rans0 < RANS_BYTE_L)
66
+ }
67
+ if (rans1 < RANS_BYTE_L) {
68
+ do {
69
+ rans1 = (rans1 << 8) | (0xff & input.get())
70
+ } while (rans1 < RANS_BYTE_L)
71
+ }
72
+ if (rans2 < RANS_BYTE_L) {
73
+ do {
74
+ rans2 = (rans2 << 8) | (0xff & input.get())
75
+ } while (rans2 < RANS_BYTE_L)
76
+ }
77
+ if (rans7 < RANS_BYTE_L) {
78
+ do {
79
+ rans7 = (rans7 << 8) | (0xff & input.get())
80
+ } while (rans7 < RANS_BYTE_L)
81
+ }
46
82
 
47
83
  l0 = c0
48
84
  l1 = c1
@@ -52,9 +88,19 @@ export default function uncompress(
52
88
 
53
89
  // Remainder
54
90
  for (; i7 < outputSize; i7 += 1) {
55
- const /* int */ c7 = 0xff & D[l7].R[Decoding.get(rans7, TF_SHIFT)]
56
- output.putAt(i7, c7)
57
- rans7 = Decoding.advanceSymbol(rans7, input, syms[l7][c7], TF_SHIFT)
91
+ const /* int */ c7 = 0xff & D[l7].R[rans7 & MASK]
92
+ // Inline putAt to avoid function call overhead
93
+ output._buffer[i7] = c7
94
+
95
+ // Inline advanceSymbol to avoid function call overhead
96
+ const sym = syms[l7][c7]
97
+ rans7 = sym.freq * (rans7 >> TF_SHIFT) + (rans7 & MASK) - sym.start
98
+ if (rans7 < RANS_BYTE_L) {
99
+ do {
100
+ rans7 = (rans7 << 8) | (0xff & input.get())
101
+ } while (rans7 < RANS_BYTE_L)
102
+ }
103
+
58
104
  l7 = c7
59
105
  }
60
106
  }
@@ -15,10 +15,9 @@ class AriDecoder {
15
15
  // byte[] R;
16
16
 
17
17
  constructor() {
18
- this.fc = new Array(256)
19
- for (let i = 0; i < this.fc.length; i += 1) {
20
- this.fc[i] = new FC()
21
- }
18
+ // Use two parallel arrays instead of array of objects for better cache efficiency
19
+ this.fcF = new Array(256)
20
+ this.fcC = new Array(256)
22
21
  this.R = null
23
22
  }
24
23
  }
@@ -19,25 +19,22 @@ export function readStatsO0(
19
19
  let x = 0
20
20
  let j = cp.get() & 0xff
21
21
  do {
22
- if (decoder.fc[j] == null) {
23
- decoder.fc[j] = new Decoding.FC()
22
+ decoder.fcF[j] = cp.get() & 0xff
23
+ if (decoder.fcF[j] >= 128) {
24
+ decoder.fcF[j] &= ~128
25
+ decoder.fcF[j] = ((decoder.fcF[j] & 127) << 8) | (cp.get() & 0xff)
24
26
  }
25
- decoder.fc[j].F = cp.get() & 0xff
26
- if (decoder.fc[j].F >= 128) {
27
- decoder.fc[j].F &= ~128
28
- decoder.fc[j].F = ((decoder.fc[j].F & 127) << 8) | (cp.get() & 0xff)
29
- }
30
- decoder.fc[j].C = x
27
+ decoder.fcC[j] = x
31
28
 
32
- Decoding.symbolInit(syms[j], decoder.fc[j].C, decoder.fc[j].F)
29
+ Decoding.symbolInit(syms[j], decoder.fcC[j], decoder.fcF[j])
33
30
 
34
31
  /* Build reverse lookup table */
35
32
  if (!decoder.R) {
36
33
  decoder.R = new Array(TOTFREQ)
37
34
  }
38
- decoder.R.fill(j, x, x + decoder.fc[j].F)
35
+ decoder.R.fill(j, x, x + decoder.fcF[j])
39
36
 
40
- x += decoder.fc[j].F
37
+ x += decoder.fcF[j]
41
38
 
42
39
  if (rle === 0 && j + 1 === (0xff & cp.getByteAt(cp.position()))) {
43
40
  j = cp.get() & 0xff
@@ -68,33 +65,30 @@ export function readStatsO1(
68
65
  D[i] = new Decoding.AriDecoder()
69
66
  }
70
67
  do {
71
- if (D[i].fc[j] == null) {
72
- D[i].fc[j] = new Decoding.FC()
73
- }
74
- D[i].fc[j].F = 0xff & cp.get()
75
- if (D[i].fc[j].F >= 128) {
76
- D[i].fc[j].F &= ~128
77
- D[i].fc[j].F = ((D[i].fc[j].F & 127) << 8) | (0xff & cp.get())
68
+ D[i].fcF[j] = 0xff & cp.get()
69
+ if (D[i].fcF[j] >= 128) {
70
+ D[i].fcF[j] &= ~128
71
+ D[i].fcF[j] = ((D[i].fcF[j] & 127) << 8) | (0xff & cp.get())
78
72
  }
79
- D[i].fc[j].C = x
73
+ D[i].fcC[j] = x
80
74
 
81
- if (D[i].fc[j].F === 0) {
82
- D[i].fc[j].F = TOTFREQ
75
+ if (D[i].fcF[j] === 0) {
76
+ D[i].fcF[j] = TOTFREQ
83
77
  }
84
78
 
85
79
  if (syms[i][j] == null) {
86
80
  syms[i][j] = new Decoding.RansDecSymbol()
87
81
  }
88
82
 
89
- Decoding.symbolInit(syms[i][j], D[i].fc[j].C, D[i].fc[j].F)
83
+ Decoding.symbolInit(syms[i][j], D[i].fcC[j], D[i].fcF[j])
90
84
 
91
85
  /* Build reverse lookup table */
92
86
  if (D[i].R == null) {
93
87
  D[i].R = new Array(TOTFREQ)
94
88
  }
95
- D[i].R.fill(j, x, x + D[i].fc[j].F)
89
+ D[i].R.fill(j, x, x + D[i].fcF[j])
96
90
 
97
- x += D[i].fc[j].F
91
+ x += D[i].fcF[j]
98
92
  assert(x <= TOTFREQ)
99
93
 
100
94
  if (rlej === 0 && j + 1 === (0xff & cp.getByteAt(cp.position()))) {