UNPKG

@csound/browser

Version:

[![npm (scoped with tag)](https://shields.shivering-isles.com/npm/v/@csound/browser/latest)](https://www.npmjs.com/package/@csound/browser) [![GitHub Workflow Status](https://shields.shivering-isles.com/github/workflow/status/csound/csound/csound_wasm)](h

811 lines (711 loc) 20.8 kB
/* eslint-disable unicorn/numeric-separators-style,camelcase,no-unused-expressions */ import { Huffman } from "./huffman"; /** @define {number} buffer block size. */ const ZLIB_RAW_INFLATE_BUFFER_SIZE = 0x8000; // [ 0x8000 >= ZLIB_BUFFER_BLOCK_SIZE ] /** * @constructor * @param {!(Uint8Array)} input * @param {Object} [opt_parameters] * * opt_params は以下のプロパティを指定する事ができます。 * - index: input buffer の deflate コンテナの開始位置. * - blockSize: バッファのブロックサイズ. * - bufferType: RawInflate.BufferType の値によってバッファの管理方法を指定する. * - resize: 確保したバッファが実際の大きさより大きかった場合に切り詰める. */ export function RawInflate(input, opt_parameters) { /** @type {!(Uint8Array)} inflated buffer */ this.buffer; /** @type {!Array.<(Uint8Array)>} */ this.blocks = []; /** @type {number} block size. */ this.bufferSize = ZLIB_RAW_INFLATE_BUFFER_SIZE; /** @type {!number} total output buffer pointer. */ this.totalpos = 0; /** @type {!number} input buffer pointer. */ this.ip = 0; /** @type {!number} bit stream reader buffer. */ this.bitsbuf = 0; /** @type {!number} bit stream reader buffer size. */ this.bitsbuflen = 0; /** @type {!(Uint8Array)} input buffer. */ this.input = new Uint8Array(input); /** @type {!(Uint8Array)} output buffer. */ this.output; /** @type {!number} output buffer pointer. */ this.op; /** @type {boolean} is final block flag. */ this.bfinal = false; /** @type {RawInflate.BufferType} buffer management. */ this.bufferType = RawInflate.BufferType.ADAPTIVE; /** @type {boolean} resize flag for memory size optimization. */ this.resize = false; // option parameters if (opt_parameters || !(opt_parameters = {})) { if (opt_parameters.index) { this.ip = opt_parameters.index; } if (opt_parameters.bufferSize) { this.bufferSize = opt_parameters.bufferSize; } if (opt_parameters.bufferType) { this.bufferType = opt_parameters.bufferType; } if (opt_parameters.resize) { this.resize = opt_parameters.resize; } } // initialize switch (this.bufferType) { case RawInflate.BufferType.BLOCK: { this.op = RawInflate.MaxBackwardLength; this.output = new Uint8Array( RawInflate.MaxBackwardLength + this.bufferSize + RawInflate.MaxCopyLength, ); break; } case RawInflate.BufferType.ADAPTIVE: { this.op = 0; this.output = new Uint8Array(this.bufferSize); break; } default: { throw new Error("invalid inflate mode"); } } } /** * @enum {number} */ RawInflate.BufferType = { BLOCK: 0, ADAPTIVE: 1, }; /** * decompress. * @return {!(Uint8Array)} inflated buffer. */ RawInflate.prototype.decompress = function () { while (!this.bfinal) { this.parseBlock(); } switch (this.bufferType) { case RawInflate.BufferType.BLOCK: { return this.concatBufferBlock(); } case RawInflate.BufferType.ADAPTIVE: { return this.concatBufferDynamic(); } default: { throw new Error("invalid inflate mode"); } } }; /** * @const * @type {number} max backward length for LZ77. */ RawInflate.MaxBackwardLength = 32768; /** * @const * @type {number} max copy length for LZ77. */ RawInflate.MaxCopyLength = 258; /** * huffman order * @const * @type {!(Uint8Array)} */ RawInflate.Order = (function (table) { return new Uint16Array(table); })([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]); /** * huffman length code table. * @const * @type {!(Uint16Array)} */ RawInflate.LengthCodeTable = (function (table) { return new Uint16Array(table); })([ 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000d, 0x000f, 0x0011, 0x0013, 0x0017, 0x001b, 0x001f, 0x0023, 0x002b, 0x0033, 0x003b, 0x0043, 0x0053, 0x0063, 0x0073, 0x0083, 0x00a3, 0x00c3, 0x00e3, 0x0102, 0x0102, 0x0102, ]); /** * huffman length extra-bits table. * @const * @type {!(Uint8Array)} */ RawInflate.LengthExtraTable = (function (table) { return new Uint8Array(table); })([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0]); /** * huffman dist code table. * @const * @type {!(Uint16Array)} */ RawInflate.DistCodeTable = (function (table) { return new Uint16Array(table); })([ 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0007, 0x0009, 0x000d, 0x0011, 0x0019, 0x0021, 0x0031, 0x0041, 0x0061, 0x0081, 0x00c1, 0x0101, 0x0181, 0x0201, 0x0301, 0x0401, 0x0601, 0x0801, 0x0c01, 0x1001, 0x1801, 0x2001, 0x3001, 0x4001, 0x6001, ]); /** * huffman dist extra-bits table. * @const * @type {!(Uint8Array)} */ RawInflate.DistExtraTable = (function (table) { return new Uint8Array(table); })([ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, ]); /** * fixed huffman length code table * @const * @type {!Array} */ RawInflate.FixedLiteralLengthTable = (function (table) { return table; })( (function () { const lengths = new Uint8Array(288); let index, il; for (index = 0, il = lengths.length; index < il; ++index) { lengths[index] = index <= 143 ? 8 : index <= 255 ? 9 : index <= 279 ? 7 : 8; } return Huffman(lengths); })(), ); /** * fixed huffman distance code table * @const * @type {!Array} */ RawInflate.FixedDistanceTable = (function (table) { return table; })( (function () { const lengths = new Uint8Array(30); let index, il; for (index = 0, il = lengths.length; index < il; ++index) { lengths[index] = 5; } return Huffman(lengths); })(), ); /** * parse deflated block. */ RawInflate.prototype.parseBlock = function () { /** @type {number} header */ let hdr = this.readBits(3); // BFINAL if (hdr & 0x1) { this.bfinal = true; } // BTYPE hdr >>>= 1; switch (hdr) { // uncompressed case 0: { this.parseUncompressedBlock(); break; } // fixed huffman case 1: { this.parseFixedHuffmanBlock(); break; } // dynamic huffman case 2: { this.parseDynamicHuffmanBlock(); break; } // reserved or other default: { throw new Error("unknown BTYPE: " + hdr); } } }; /** * read inflate bits * @param {number} length bits length. * @return {number} read bits. */ RawInflate.prototype.readBits = function (length) { let bitsbuf = this.bitsbuf; let bitsbuflen = this.bitsbuflen; const input = this.input; let ip = this.ip; /** @type {number} */ const inputLength = input.length; if (ip + ((length - bitsbuflen + 7) >> 3) >= inputLength) { throw new Error("input buffer is broken"); } // not enough buffer while (bitsbuflen < length) { bitsbuf |= input[ip++] << bitsbuflen; bitsbuflen += 8; } /** @type {number} input and output byte. */ const octet = bitsbuf & /* MASK */ ((1 << length) - 1); bitsbuf >>>= length; bitsbuflen -= length; this.bitsbuf = bitsbuf; this.bitsbuflen = bitsbuflen; this.ip = ip; return octet; }; /** * read huffman code using table * @param {Array.<number>|Uint8Array|Uint16Array|null} table huffman code table. * @return {number} huffman code. */ RawInflate.prototype.readCodeByTable = function (table) { let bitsbuf = this.bitsbuf; let bitsbuflen = this.bitsbuflen; const input = this.input; let ip = this.ip; /** @type {number} */ const inputLength = input.length; /** @type {!(Uint8Array)} huffman code table */ const codeTable = table[0]; /** @type {number} */ const maxCodeLength = table[1]; // not enough buffer while (bitsbuflen < maxCodeLength) { if (ip >= inputLength) { break; } bitsbuf |= input[ip++] << bitsbuflen; bitsbuflen += 8; } // read max length /** @type {number} code length & code (16bit, 16bit) */ const codeWithLength = codeTable[bitsbuf & ((1 << maxCodeLength) - 1)]; /** @type {number} code bits length */ const codeLength = codeWithLength >>> 16; if (codeLength > bitsbuflen) { throw new Error("invalid code length: " + codeLength); } this.bitsbuf = bitsbuf >> codeLength; this.bitsbuflen = bitsbuflen - codeLength; this.ip = ip; return codeWithLength & 0xffff; }; /** * parse uncompressed block. */ RawInflate.prototype.parseUncompressedBlock = function () { const input = this.input; let ip = this.ip; let output = this.output; let op = this.op; /** @type {number} */ const inputLength = input.length; /** @type {number} block length */ let length_; /** @type {number} output buffer length */ const olength = output.length; /** @type {number} copy counter */ let preCopy; // skip buffered header bits this.bitsbuf = 0; this.bitsbuflen = 0; // len if (ip + 1 >= inputLength) { throw new Error("invalid uncompressed block header: LEN"); } length_ = input[ip++] | (input[ip++] << 8); // nlen if (ip + 1 >= inputLength) { throw new Error("invalid uncompressed block header: NLEN"); } /** @type {number} number for check block length */ const nlen = input[ip++] | (input[ip++] << 8); // check len & nlen if (length_ === ~nlen) { throw new Error("invalid uncompressed block header: length verify"); } // check size if (ip + length_ > input.length) { throw new Error("input buffer is broken"); } // expand buffer switch (this.bufferType) { case RawInflate.BufferType.BLOCK: { // pre copy while (op + length_ > output.length) { preCopy = olength - op; length_ -= preCopy; output.set(input.subarray(ip, ip + preCopy), op); op += preCopy; ip += preCopy; this.op = op; output = this.expandBufferBlock(); op = this.op; } break; } case RawInflate.BufferType.ADAPTIVE: { while (op + length_ > output.length) { output = this.expandBufferAdaptive({ fixRatio: 2 }); } break; } default: { throw new Error("invalid inflate mode"); } } // copy output.set(input.subarray(ip, ip + length_), op); op += length_; ip += length_; this.ip = ip; this.op = op; this.output = output; }; /** * parse fixed huffman block. */ RawInflate.prototype.parseFixedHuffmanBlock = function () { switch (this.bufferType) { case RawInflate.BufferType.ADAPTIVE: { this.decodeHuffmanAdaptive(RawInflate.FixedLiteralLengthTable, RawInflate.FixedDistanceTable); break; } case RawInflate.BufferType.BLOCK: { this.decodeHuffmanBlock(RawInflate.FixedLiteralLengthTable, RawInflate.FixedDistanceTable); break; } default: { throw new Error("invalid inflate mode"); } } }; /** * parse dynamic huffman block. */ RawInflate.prototype.parseDynamicHuffmanBlock = function () { /** @type {number} number of literal and length codes. */ const hlit = this.readBits(5) + 257; /** @type {number} number of distance codes. */ const hdist = this.readBits(5) + 1; /** @type {number} number of code lengths. */ const hclen = this.readBits(4) + 4; /** @type {Uint8Array} code lengths. */ const codeLengths = new Uint8Array(RawInflate.Order.length); /** @type {number} */ let code; /** @type {number} */ let previous; /** @type {number} */ let repeat; /** @type {number} loop counter. */ let index; /** @type {number} loop limit. */ let il; // decode code lengths for (index = 0; index < hclen; ++index) { codeLengths[RawInflate.Order[index]] = this.readBits(3); } // decode length table /** @type {Array.<number,number,number>} code lengths table. */ const codeLengthsTable = Huffman(codeLengths); /** @type {Uint8Array} code length table. */ const lengthTable = new Uint8Array(hlit + hdist); for (index = 0, il = hlit + hdist; index < il; ) { code = this.readCodeByTable(codeLengthsTable); switch (code) { case 16: { repeat = 3 + this.readBits(2); while (repeat--) { lengthTable[index++] = previous; } break; } case 17: { repeat = 3 + this.readBits(3); while (repeat--) { lengthTable[index++] = 0; } previous = 0; break; } case 18: { repeat = 11 + this.readBits(7); while (repeat--) { lengthTable[index++] = 0; } previous = 0; break; } default: { lengthTable[index++] = code; previous = code; break; } } } /** @type {Array.<number>|Uint8Array|null} literal and length code table. */ const litlenTable = Huffman(lengthTable.subarray(0, hlit)); /** @type {Array.<number>|Uint8Array} distance code table. */ const distTable = Huffman(lengthTable.subarray(hlit)); switch (this.bufferType) { case RawInflate.BufferType.ADAPTIVE: { this.decodeHuffmanAdaptive(litlenTable, distTable); break; } case RawInflate.BufferType.BLOCK: { this.decodeHuffmanBlock(litlenTable, distTable); break; } default: { throw new Error("invalid inflate mode"); } } }; /** * decode huffman code * @param {Array.<number>|Uint8Array|Uint16Array} litlen literal and length code table. * @param {Array.<number>|Uint8Array} dist distination code table. */ RawInflate.prototype.decodeHuffmanBlock = function (litlen, dist) { let output = this.output; let op = this.op; this.currentLitlenTable = litlen; /** @type {number} output position limit. */ const olength = output.length - RawInflate.MaxCopyLength; /** @type {number} huffman code. */ let code; /** @type {number} table index. */ let ti; /** @type {number} huffman code distination. */ let codeDist; /** @type {number} huffman code length. */ let codeLength; const lengthCodeTable = RawInflate.LengthCodeTable; const lengthExtraTable = RawInflate.LengthExtraTable; const distCodeTable = RawInflate.DistCodeTable; const distExtraTable = RawInflate.DistExtraTable; while ((code = this.readCodeByTable(litlen)) !== 256) { // literal if (code < 256) { if (op >= olength) { this.op = op; output = this.expandBufferBlock(); op = this.op; } output[op++] = code; continue; } // length code ti = code - 257; codeLength = lengthCodeTable[ti]; if (lengthExtraTable[ti] > 0) { codeLength += this.readBits(lengthExtraTable[ti]); } // dist code code = this.readCodeByTable(dist); codeDist = distCodeTable[code]; if (distExtraTable[code] > 0) { codeDist += this.readBits(distExtraTable[code]); } // lz77 decode if (op >= olength) { this.op = op; output = this.expandBufferBlock(); op = this.op; } while (codeLength--) { output[op] = output[op++ - codeDist]; } } while (this.bitsbuflen >= 8) { this.bitsbuflen -= 8; this.ip--; } this.op = op; }; /** * decode huffman code (adaptive) * @param {Array.<number>|Uint8Array|Uint16Array} litlen literal and length code table. * @param {Array.<number>|Uint8Array} dist distination code table. */ RawInflate.prototype.decodeHuffmanAdaptive = function (litlen, dist) { let output = this.output; let op = this.op; this.currentLitlenTable = litlen; /** @type {number} output position limit. */ let olength = output.length; /** @type {number} huffman code. */ let code; /** @type {number} table index. */ let ti; /** @type {number} huffman code distination. */ let codeDist; /** @type {number} huffman code length. */ let codeLength; const lengthCodeTable = RawInflate.LengthCodeTable; const lengthExtraTable = RawInflate.LengthExtraTable; const distCodeTable = RawInflate.DistCodeTable; const distExtraTable = RawInflate.DistExtraTable; while ((code = this.readCodeByTable(litlen)) !== 256) { // literal if (code < 256) { if (op >= olength) { output = this.expandBufferAdaptive(); olength = output.length; } output[op++] = code; continue; } // length code ti = code - 257; codeLength = lengthCodeTable[ti]; if (lengthExtraTable[ti] > 0) { codeLength += this.readBits(lengthExtraTable[ti]); } // dist code code = this.readCodeByTable(dist); codeDist = distCodeTable[code]; if (distExtraTable[code] > 0) { codeDist += this.readBits(distExtraTable[code]); } // lz77 decode if (op + codeLength > olength) { output = this.expandBufferAdaptive(); olength = output.length; } while (codeLength--) { output[op] = output[op++ - codeDist]; } } while (this.bitsbuflen >= 8) { this.bitsbuflen -= 8; this.ip--; } this.op = op; }; /** * expand output buffer. * @param {Object} [opt_parameter] * @return {!(Uint8Array)} output buffer. */ RawInflate.prototype.expandBufferBlock = function (opt_parameter) { /** @type {!(Uint8Array)} store buffer. */ const buffer = new Uint8Array(this.op - RawInflate.MaxBackwardLength); /** @type {number} backward base point */ const backward = this.op - RawInflate.MaxBackwardLength; const output = this.output; // copy to output buffer buffer.set(output.subarray(RawInflate.MaxBackwardLength, buffer.length)); this.blocks.push(buffer); this.totalpos += buffer.length; // copy to backward buffer output.set(output.subarray(backward, backward + RawInflate.MaxBackwardLength)); this.op = RawInflate.MaxBackwardLength; return output; }; /** * expand output buffer. (adaptive) * @param {Object=} opt_parameter * @return {!(Uint8Array)} output buffer pointer. */ RawInflate.prototype.expandBufferAdaptive = function (opt_parameter) { /** @type {number} expantion ratio. */ let ratio = Math.trunc(this.input.length / this.ip + 1); /** @type {number} maximum number of huffman code. */ let maxHuffCode; /** @type {number} new output buffer size. */ let newSize; /** @type {number} max inflate size. */ let maxInflateSize; const input = this.input; const output = this.output; if (opt_parameter) { if (typeof opt_parameter.fixRatio === "number") { ratio = opt_parameter.fixRatio; } if (typeof opt_parameter.addRatio === "number") { ratio += opt_parameter.addRatio; } } // calculate new buffer size if (ratio < 2) { maxHuffCode = (input.length - this.ip) / this.currentLitlenTable[2]; maxInflateSize = Math.trunc((maxHuffCode / 2) * 258); newSize = maxInflateSize < output.length ? output.length + maxInflateSize : output.length << 1; } else { newSize = output.length * ratio; } // buffer expantion /** @type {!(Uint8Array)} store buffer. */ const buffer = new Uint8Array(newSize); buffer.set(output); this.output = buffer; return this.output; }; /** * concat output buffer. * @return {!(Uint8Array)} output buffer. */ RawInflate.prototype.concatBufferBlock = function () { /** @type {number} buffer pointer. */ let pos = 0; /** @type {number} buffer pointer. */ const limit = this.totalpos + (this.op - RawInflate.MaxBackwardLength); /** @type {!(Uint8Array)} output block array. */ const output = this.output; /** @type {!Array} blocks array. */ const blocks = this.blocks; /** @type {!(Uint8Array)} output block array. */ let block; /** @type {!(Uint8Array)} output buffer. */ const buffer = new Uint8Array(limit); /** @type {number} loop counter. */ let index; /** @type {number} loop limiter. */ let il; /** @type {number} loop counter. */ let index_; /** @type {number} loop limiter. */ let jl; // single buffer if (blocks.length === 0) { return this.output.subarray(RawInflate.MaxBackwardLength, this.op); } // copy to buffer for (index = 0, il = blocks.length; index < il; ++index) { block = blocks[index]; for (index_ = 0, jl = block.length; index_ < jl; ++index_) { buffer[pos++] = block[index_]; } } // current buffer for (index = RawInflate.MaxBackwardLength, il = this.op; index < il; ++index) { buffer[pos++] = output[index]; } this.blocks = []; this.buffer = buffer; return this.buffer; }; /** * concat output buffer. (dynamic) * @return {!(Uint8Array)} output buffer. */ RawInflate.prototype.concatBufferDynamic = function () { /** @type {Uint8Array} output buffer. */ let buffer; const op = this.op; if (this.resize) { buffer = new Uint8Array(op); buffer.set(this.output.subarray(0, op)); } else { buffer = this.output.subarray(0, op); } this.buffer = buffer; return this.buffer; };