UNPKG

@gmod/cram

Version:

read CRAM files with pure Javascript

162 lines 6.44 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const _base_ts_1 = __importDefault(require("./_base.js")); const errors_ts_1 = require("../../errors.js"); /** * Inlined getBits for huffman decoding - avoids function call overhead */ function getBitsInline(data, cursor, numBits) { let { bytePosition, bitPosition } = cursor; // Fast path for single bit (common in huffman) if (numBits === 1) { const val = (data[bytePosition] >> bitPosition) & 1; bitPosition -= 1; if (bitPosition < 0) { bytePosition += 1; bitPosition = 7; } cursor.bytePosition = bytePosition; cursor.bitPosition = bitPosition; return val; } // General case let val = 0; for (let i = 0; i < numBits; i++) { val <<= 1; val |= (data[bytePosition] >> bitPosition) & 1; bitPosition -= 1; if (bitPosition < 0) { bytePosition += 1; bitPosition = 7; } } cursor.bytePosition = bytePosition; cursor.bitPosition = bitPosition; return val; } function numberOfSetBits(ii) { let i = (ii - (ii >> 1)) & 0x55555555; i = (i & 0x33333333) + ((i >> 2) & 0x33333333); return (((i + (i >> 4)) & 0x0f0f0f0f) * 0x01010101) >> 24; } class HuffmanIntCodec extends _base_ts_1.default { codes = {}; codeBook = {}; sortedCodes = []; // parallel flat arrays indexed by position in sortedCodes; flat arrays let // V8 use monomorphic inline caches in the decode hot path valuesByIndex = []; bitLengthsByIndex = []; // sparse lookup: bitCode -> index in sortedCodes, or -1 if absent bitCodeToIndex = []; constructor(parameters, dataType) { super(parameters, dataType); if (!['byte', 'int'].includes(this.dataType)) { throw new TypeError(`${this.dataType} decoding not yet implemented by HUFFMAN_INT codec`); } this.buildCodeBook(); this.buildCodes(); this.buildCaches(); // degenerate zero-length huffman code: special-case the decoding. // empty codeBook (no codes at all) is also valid for unused data series; // decode() will throw 'Huffman symbol not found' if such a codec is used. if (this.sortedCodes.length > 0 && this.sortedCodes[0].bitLength === 0) { this._decode = this._decodeZeroLengthCode; } } buildCodeBook() { // parse the parameters together into a `codes` data structure const codes = new Array(this.parameters.numCodes); for (let i = 0; i < this.parameters.numCodes; i++) { codes[i] = { symbol: this.parameters.symbols[i], bitLength: this.parameters.bitLengths[i], }; } // sort the codes by bit length and symbol value codes.sort((a, b) => a.bitLength - b.bitLength || a.symbol - b.symbol); this.codeBook = {}; codes.forEach(code => { if (!this.codeBook[code.bitLength]) { this.codeBook[code.bitLength] = []; } this.codeBook[code.bitLength].push(code.symbol); }); } buildCodes() { this.codes = {}; /* new TreeMap<Integer, HuffmanBitCode>(); */ let codeLength = 0; let codeValue = -1; Object.entries(this.codeBook).forEach(([bitLength, symbols]) => { const bitLengthInt = Number.parseInt(bitLength, 10); symbols.forEach(symbol => { const code = { bitLength: bitLengthInt, value: symbol, bitCode: 0, }; codeValue = codeValue + 1; const delta = bitLengthInt - codeLength; // new length? codeValue = codeValue << delta; // pad with 0's code.bitCode = codeValue; // calculated: huffman code codeLength = codeLength + delta; // adjust current code length if (numberOfSetBits(codeValue) > bitLengthInt) { throw new errors_ts_1.CramMalformedError('Symbol out of range'); } this.codes[symbol] = code; }); }); } buildCaches() { this.sortedCodes = Object.values(this.codes).sort((a, b) => a.bitLength - b.bitLength || a.bitCode - b.bitCode); this.valuesByIndex = this.sortedCodes.map(c => c.value); this.bitLengthsByIndex = this.sortedCodes.map(c => c.bitLength); if (this.sortedCodes.length > 0) { let maxBitCode = 0; for (const code of this.sortedCodes) { if (code.bitCode > maxBitCode) { maxBitCode = code.bitCode; } } this.bitCodeToIndex = new Array(maxBitCode + 1).fill(-1); for (let i = 0; i < this.sortedCodes.length; i++) { this.bitCodeToIndex[this.sortedCodes[i].bitCode] = i; } } } decode(slice, coreDataBlock, _blocksByContentId, cursors) { return this._decode(slice, coreDataBlock, cursors.coreBlock); } // the special case for zero-length codes _decodeZeroLengthCode() { return this.sortedCodes[0].value; } _decode(_slice, coreDataBlock, coreCursor) { const input = coreDataBlock.content; let prevLen = 0; let bits = 0; for (let i = 0; i < this.sortedCodes.length; i += 1) { const length = this.sortedCodes[i].bitLength; const bitsToRead = length - prevLen; if (bitsToRead > 0) { bits <<= bitsToRead; bits |= getBitsInline(input, coreCursor, bitsToRead); } prevLen = length; const index = this.bitCodeToIndex[bits] ?? -1; if (index > -1 && this.bitLengthsByIndex[index] === length) { return this.valuesByIndex[index]; } while (i + 1 < this.sortedCodes.length && this.sortedCodes[i + 1].bitLength === length) { i += 1; } } throw new errors_ts_1.CramMalformedError('Huffman symbol not found.'); } } exports.default = HuffmanIntCodec; //# sourceMappingURL=huffman.js.map