/* * HCA block decoder. * * Per encoded block: * 1. Optional cipher decrypt (in-place on a per-block copy). * 2. Read sync magic = 0xFFFF + 16-bit "a" value. * 3. For each channel: decode1 reads scale-factors + quant * lookup state. * 4. For each of 8 sub-frames: * decode2 read quantised coefficients into channel.block * decode3 intensity-stereo / joint-stereo expand * decode4 M/S coupling * decode5 inverse MDCT + windowing → 128 PCM samples per * channel into channel.wave[sub]. * 5. Caller interleaves 8 × 128 = 1024 PCM samples per channel * into the global output buffer. * * All the big static tables (DECODE1..DECODE5) are stored as * `readonly` arrays of the original IEEE-754 bit patterns and * converted to Float32 once at module load. * * Ported from kohos/CriTools (MIT) — https://github.com/kohos/CriTools */ import { parseHca, type HcaHeader, HcaParseError } from './parse.js'; import { initCiphTable, decryptBlock, combineAwbKey, splitKey64, } from './decrypt.js'; /** Options for {@link decodeHca}. */ export interface DecodeOptions { /** * 64-bit HCA cipher key as a `bigint`. Unused for type-0 * (unencrypted) and type-1 (static-table) cipher headers. */ key?: bigint; /** * AWB sub-key. Combined with `key` for the final 64-bit * cipher seed when the HCA was extracted from an AWB. * Unused for standalone HCAs. */ awbKey?: number; /** Volume multiplier; defaults to 1.0. */ volume?: number; } export interface DecodedHca { channelCount: number; samplingRate: number; /** Total number of samples per channel (NOT total floats). */ samplesPerChannel: number; /** * Interleaved Float32 PCM: `[ch0_s0, ch1_s0, ch0_s1, ...]`. * Length is `samplesPerChannel * channelCount`. */ pcm: Float32Array; loopStart?: number; loopEnd?: number; } /** * Decode an HCA file end-to-end into a flat Float32 PCM buffer. */ export function decodeHca( bytes: Uint8Array, options: DecodeOptions = {}, ): DecodedHca { const volume = options.volume ?? 1.0; const header = parseHca(bytes); // Resolve the cipher key. let key1 = 0; let key2 = 0; if (options.key !== undefined) { let k = options.key & 0xffffffffffffffffn; if (options.awbKey) k = combineAwbKey(k, options.awbKey); [key1, key2] = splitKey64(k); } // Build the ATH and CIPH tables once per decode. const athTable = new Uint8Array(0x80); if (!initAthTable(athTable, header.athType, header.samplingRate)) { throw new HcaParseError(`Bad athType: ${header.athType}`); } const ciphTable = new Uint8Array(0x100); if (!initCiphTable(ciphTable, header.ciphType, key1, key2)) { throw new HcaParseError(`Bad ciphType: ${header.ciphType}`); } const state = initDecode(header); const samplesPerChannel = header.blockCount * 8 * 0x80; const pcm = new Float32Array(samplesPerChannel * header.channelCount); const baseVolume = (header.volume ?? 1.0) * volume; let address = header.dataOffset; let n = 0; for (let m = 0; m < header.blockCount; m++) { decodeBlock(header, state, athTable, ciphTable, bytes, address); // Interleave 8 × 128 per channel → flat output. for (let i = 0; i < 8; i++) { for (let j = 0; j < 0x80; j++) { for (let k = 0; k < header.channelCount; k++) { pcm[n++] = state.channels[k]!.wave[i]![j]! * baseVolume; } } } address += header.blockSize; } return { channelCount: header.channelCount, samplingRate: header.samplingRate, samplesPerChannel, pcm, loopStart: header.loopStart, loopEnd: header.loopEnd, }; } // --------------------------------------------------------------------------- // ATH table // --------------------------------------------------------------------------- const ATH_LIST_1: readonly number[] = [ 0x78, 0x5f, 0x56, 0x51, 0x4e, 0x4c, 0x4b, 0x49, 0x48, 0x48, 0x47, 0x46, 0x46, 0x45, 0x45, 0x45, 0x44, 0x44, 0x44, 0x44, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x50, 0x50, 0x50, 0x50, 0x50, 0x51, 0x51, 0x51, 0x51, 0x51, 0x52, 0x52, 0x52, 0x52, 0x52, 0x53, 0x53, 0x53, 0x53, 0x54, 0x54, 0x54, 0x54, 0x54, 0x55, 0x55, 0x55, 0x55, 0x56, 0x56, 0x56, 0x56, 0x57, 0x57, 0x57, 0x57, 0x57, 0x58, 0x58, 0x58, 0x59, 0x59, 0x59, 0x59, 0x5a, 0x5a, 0x5a, 0x5a, 0x5b, 0x5b, 0x5b, 0x5b, 0x5c, 0x5c, 0x5c, 0x5d, 0x5d, 0x5d, 0x5d, 0x5e, 0x5e, 0x5e, 0x5f, 0x5f, 0x5f, 0x60, 0x60, 0x60, 0x61, 0x61, 0x61, 0x61, 0x62, 0x62, 0x62, 0x63, 0x63, 0x63, 0x64, 0x64, 0x64, 0x65, 0x65, 0x66, 0x66, 0x66, 0x67, 0x67, 0x67, 0x68, 0x68, 0x68, 0x69, 0x69, 0x6a, 0x6a, 0x6a, 0x6b, 0x6b, 0x6b, 0x6c, 0x6c, 0x6d, 0x6d, 0x6d, 0x6e, 0x6e, 0x6f, 0x6f, 0x70, 0x70, 0x70, 0x71, 0x71, 0x72, 0x72, 0x73, 0x73, 0x73, 0x74, 0x74, 0x75, 0x75, 0x76, 0x76, 0x77, 0x77, 0x78, 0x78, 0x78, 0x79, 0x79, 0x7a, 0x7a, 0x7b, 0x7b, 0x7c, 0x7c, 0x7d, 0x7d, 0x7e, 0x7e, 0x7f, 0x7f, 0x80, 0x80, 0x81, 0x81, 0x82, 0x83, 0x83, 0x84, 0x84, 0x85, 0x85, 0x86, 0x86, 0x87, 0x88, 0x88, 0x89, 0x89, 0x8a, 0x8a, 0x8b, 0x8c, 0x8c, 0x8d, 0x8d, 0x8e, 0x8f, 0x8f, 0x90, 0x90, 0x91, 0x92, 0x92, 0x93, 0x94, 0x94, 0x95, 0x95, 0x96, 0x97, 0x97, 0x98, 0x99, 0x99, 0x9a, 0x9b, 0x9b, 0x9c, 0x9d, 0x9d, 0x9e, 0x9f, 0xa0, 0xa0, 0xa1, 0xa2, 0xa2, 0xa3, 0xa4, 0xa5, 0xa5, 0xa6, 0xa7, 0xa7, 0xa8, 0xa9, 0xaa, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xae, 0xaf, 0xb0, 0xb1, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xff, 0xff, ]; function initAthTable( table: Uint8Array, type: number, key: number, ): boolean { if (type === 0) { table.fill(0, 0, 0x80); return true; } if (type === 1) { let v = 0; for (let i = 0; i < 0x80; i++) { const index = v >>> 13; if (index >= 0x28e) { const last = 0x80 - i; for (let j = 0; j < last; j++) table[i + j] = 0xff; break; } table[i] = ATH_LIST_1[index]!; // v += key (operates mod 2^32, but we only use the // top 13+ bits, so plain Number arithmetic is fine). v = (v + key) >>> 0; } return true; } return false; } // --------------------------------------------------------------------------- // Decoder state // --------------------------------------------------------------------------- interface ChannelState { block: Float32Array; base: Float32Array; /** Per-coefficient scale-factor (`channel.value`). */ value: Uint8Array; /** Quantiser bits per coefficient (`channel.scale`). */ scale: Uint8Array; /** Intensity-stereo state (`channel.value2`). */ value2: Uint8Array; /** * Slice of `value` starting at `r06+r07`. Stays as a * mutable view onto the same backing storage as `value`. */ value3: Uint8Array; type: number; count: number; wav1: Float32Array; wav2: Float32Array; wav3: Float32Array; wave: Float32Array[]; } interface CompState { r01: number; r02: number; r03: number; r04: number; r05: number; r06: number; r07: number; r08: number; r09: number; } interface DecoderState { comp: CompState; channels: ChannelState[]; } function ceil2(a: number, b: number): number { return b > 0 ? Math.floor(a / b) + (a % b ? 1 : 0) : 0; } function initDecode(header: HcaHeader): DecoderState { const isComp = header.compdec === 'comp'; const r05 = isComp ? header.r05 : (header.count1 ?? 0) + 1; const r06 = isComp ? header.r06 : header.enableCount2 ? (header.count2 ?? 0) + 1 : (header.count1 ?? 0) + 1; const r07 = isComp ? header.r07 : r05 - r06; const r08 = isComp ? header.r08 : 0; let r03 = header.r03; if (!r03) r03 = 1; if (!(header.r01 === 1 && header.r02 === 15)) { throw new HcaParseError( `Unsupported r01/r02 combination: ${header.r01}/${header.r02}`, ); } const r09 = ceil2(r05 - (r06 + r07), r08); // Channel type assignment per sub-channel-group of size `b`. const r = new Uint8Array(0x10); const b = Math.floor(header.channelCount / r03); if (r07 && b > 1) { let c = 0; for (let i = 0; i < r03; i++) { switch (b) { case 2: r[c] = 1; r[c + 1] = 2; break; case 3: r[c] = 1; r[c + 1] = 2; break; case 4: r[c] = 1; r[c + 1] = 2; if (header.r04 === 0) { r[c + 2] = 1; r[c + 3] = 2; } break; case 5: r[c] = 1; r[c + 1] = 2; if (header.r04 <= 2) { r[c + 3] = 1; r[c + 4] = 2; } break; case 6: r[c] = 1; r[c + 1] = 2; r[c + 4] = 1; r[c + 5] = 2; break; case 7: r[c] = 1; r[c + 1] = 2; r[c + 4] = 1; r[c + 5] = 2; break; case 8: r[c] = 1; r[c + 1] = 2; r[c + 4] = 1; r[c + 5] = 2; r[c + 6] = 1; r[c + 7] = 2; break; } c += b; } } const channels: ChannelState[] = []; for (let i = 0; i < header.channelCount; i++) { const value = new Uint8Array(0x80); const channel: ChannelState = { block: new Float32Array(0x80), base: new Float32Array(0x80), value, scale: new Uint8Array(0x80), value2: new Uint8Array(8), value3: value.subarray(r06 + r07), type: r[i]!, count: r06 + (r[i] !== 2 ? r07 : 0), wav1: new Float32Array(0x80), wav2: new Float32Array(0x80), wav3: new Float32Array(0x80), wave: [ new Float32Array(0x80), new Float32Array(0x80), new Float32Array(0x80), new Float32Array(0x80), new Float32Array(0x80), new Float32Array(0x80), new Float32Array(0x80), new Float32Array(0x80), ], }; channels.push(channel); } return { comp: { r01: header.r01, r02: header.r02, r03, r04: header.r04, r05, r06, r07, r08, r09, }, channels, }; } // --------------------------------------------------------------------------- // Bit reader // --------------------------------------------------------------------------- const BIT_MASK: readonly number[] = [ 0xffffff, 0x7fffff, 0x3fffff, 0x1fffff, 0x0fffff, 0x07ffff, 0x03ffff, 0x01ffff, ]; class BlockReader { data: Uint8Array; size: number; bit: number; constructor(buffer: Uint8Array) { this.data = buffer; // Total readable bits = (length * 8) - 16, with the trailing // 16 bits being the block checksum (BE u16). this.size = buffer.length * 8 - 16; this.bit = 0; } checkBit(bitSize: number): number { let v = 0; if (this.bit + bitSize <= this.size) { const pos = this.bit >>> 3; v = this.data[pos]!; v = (v << 8) | this.data[pos + 1]!; v = (v << 8) | this.data[pos + 2]!; v &= BIT_MASK[this.bit & 7]!; v >>>= 24 - (this.bit & 7) - bitSize; } return v; } getBit(bitSize: number): number { const v = this.checkBit(bitSize); this.bit += bitSize; return v; } addBit(bitSize: number): void { this.bit += bitSize; } } // --------------------------------------------------------------------------- // Static float tables — stored as u32 IEEE-754 bit patterns and decoded // once on module load. // --------------------------------------------------------------------------- function intsToFloats(ints: readonly number[]): Float32Array { const out = new Float32Array(ints.length); const tmp = new ArrayBuffer(4); const dv = new DataView(tmp); for (let i = 0; i < ints.length; i++) { dv.setUint32(0, ints[i]! >>> 0, false); out[i] = dv.getFloat32(0, false); } return out; } // DECODE1 ------------------------------------------------------------------ const DECODE1_SCALELIST: readonly number[] = [ 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x07, 0x06, 0x06, 0x05, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; const DECODE1_VALUEFLOAT = intsToFloats([ 0x342a8d26, 0x34633f89, 0x3497657d, 0x34c9b9be, 0x35066491, 0x353311c4, 0x356e9910, 0x359ef532, 0x35d3ccf1, 0x360d1adf, 0x363c034a, 0x367a83b3, 0x36a6e595, 0x36de60f5, 0x371426ff, 0x3745672a, 0x37838359, 0x37af3b79, 0x37e97c38, 0x381b8d3a, 0x384f4319, 0x388a14d5, 0x38b7fbf0, 0x38f5257d, 0x3923520f, 0x39599d16, 0x3990fa4d, 0x39c12c4d, 0x3a00b1ed, 0x3a2b7a3a, 0x3a647b6d, 0x3a9837f0, 0x3acad226, 0x3b071f62, 0x3b340aaf, 0x3b6fe4ba, 0x3b9fd228, 0x3bd4f35b, 0x3c0ddf04, 0x3c3d08a4, 0x3c7bdfed, 0x3ca7cd94, 0x3cdf9613, 0x3d14f4f0, 0x3d467991, 0x3d843a29, 0x3db02f0e, 0x3deac0c7, 0x3e1c6573, 0x3e506334, 0x3e8ad4c6, 0x3eb8fbaf, 0x3ef67a41, 0x3f243516, 0x3f5acb94, 0x3f91c3d3, 0x3fc238d2, 0x400164d2, 0x402c6897, 0x4065b907, 0x40990b88, 0x40cbec15, 0x4107db35, 0x413504f3, ]); const DECODE1_SCALEFLOAT = intsToFloats([ 0x00000000, 0x3f2aaaab, 0x3ecccccd, 0x3e924925, 0x3e638e39, 0x3e3a2e8c, 0x3e1d89d9, 0x3e088889, 0x3d842108, 0x3d020821, 0x3c810204, 0x3c008081, 0x3b804020, 0x3b002008, 0x3a801002, 0x3a000801, ]); function decode1( channel: ChannelState, reader: BlockReader, a: number, b: number, athTable: Uint8Array, ): void { let v = reader.getBit(3); if (v >= 6) { for (let i = 0; i < channel.count; i++) { channel.value[i] = reader.getBit(6); } } else if (v) { let v1 = reader.getBit(6); const v2 = (1 << v) - 1; const v3 = v2 >>> 1; channel.value[0] = v1; for (let i = 1; i < channel.count; i++) { const v4 = reader.getBit(v); if (v4 !== v2) v1 += v4 - v3; else v1 = reader.getBit(6); channel.value[i] = v1; } } else { channel.value.fill(0); } if (channel.type === 2) { v = reader.checkBit(4); channel.value2[0] = v; if (v < 15) { for (let i = 0; i < 8; i++) channel.value2[i] = reader.getBit(4); } } else { for (let i = 0; i < a; i++) { channel.value3[i] = reader.getBit(6); } } for (let i = 0; i < channel.count; i++) { v = channel.value[i]!; if (v) { v = athTable[i]! + ((b + i) >>> 8) - Math.floor((v * 5) / 2) + 1; if (v < 0) v = 15; else if (v >= 0x39) v = 1; else v = DECODE1_SCALELIST[v]!; } channel.scale[i] = v; } channel.scale.fill(0, channel.count, 0x80); for (let i = 0; i < channel.count; i++) { channel.base[i] = DECODE1_VALUEFLOAT[channel.value[i]!]! * DECODE1_SCALEFLOAT[channel.scale[i]!]!; } } // DECODE2 ------------------------------------------------------------------ const DECODE2_LIST1: readonly number[] = [ 0, 2, 3, 3, 4, 4, 4, 4, 5, 6, 7, 8, 9, 10, 11, 12, ]; const DECODE2_LIST2: readonly number[] = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, ]; const DECODE2_LIST3: readonly number[] = [ +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +1, -1, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +1, +1, -1, -1, +2, -2, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +1, -1, +2, -2, +3, -3, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +1, +1, -1, -1, +2, +2, -2, -2, +3, +3, -3, -3, +4, -4, +0, +0, +1, +1, -1, -1, +2, +2, -2, -2, +3, -3, +4, -4, +5, -5, +0, +0, +1, +1, -1, -1, +2, -2, +3, -3, +4, -4, +5, -5, +6, -6, +0, +0, +1, -1, +2, -2, +3, -3, +4, -4, +5, -5, +6, -6, +7, -7, ]; function decode2(channel: ChannelState, reader: BlockReader): void { for (let i = 0; i < channel.count; i++) { let f: number; const s = channel.scale[i]!; const bitSize = DECODE2_LIST1[s]!; let v = reader.getBit(bitSize); if (s < 8) { v += s << 4; reader.addBit(DECODE2_LIST2[v]! - bitSize); f = DECODE2_LIST3[v]!; } else { v = (1 - ((v & 1) << 1)) * Math.floor(v / 2); if (!v) reader.addBit(-1); f = v; } channel.block[i] = channel.base[i]! * f; } channel.block.fill(0, channel.count, 0x80); } // DECODE3 ------------------------------------------------------------------ const DECODE3_LISTFLOAT = intsToFloats([ 0x00000000, 0x00000000, 0x32a0b051, 0x32d61b5e, 0x330ea43a, 0x333e0f68, 0x337d3e0c, 0x33a8b6d5, 0x33e0ccdf, 0x3415c3ff, 0x34478d75, 0x3484f1f6, 0x34b123f6, 0x34ec0719, 0x351d3eda, 0x355184df, 0x358b95c2, 0x35b9fcd2, 0x35f7d0df, 0x36251958, 0x365bfbb8, 0x36928e72, 0x36c346cd, 0x370218af, 0x372d583f, 0x3766f85b, 0x3799e046, 0x37cd078c, 0x3808980f, 0x38360094, 0x38728177, 0x38a18faf, 0x38d744fd, 0x390f6a81, 0x393f179a, 0x397e9e11, 0x39a9a15b, 0x39e2055b, 0x3a16942d, 0x3a48a2d8, 0x3a85aac3, 0x3ab21a32, 0x3aed4f30, 0x3b1e196e, 0x3b52a81e, 0x3b8c57ca, 0x3bbaff5b, 0x3bf9295a, 0x3c25fed7, 0x3c5d2d82, 0x3c935a2b, 0x3cc4563f, 0x3d02cd87, 0x3d2e4934, 0x3d68396a, 0x3d9ab62b, 0x3dce248c, 0x3e0955ee, 0x3e36fd92, 0x3e73d290, 0x3ea27043, 0x3ed87039, 0x3f1031dc, 0x3f40213b, 0x3f800000, 0x3faa8d26, 0x3fe33f89, 0x4017657d, 0x4049b9be, 0x40866491, 0x40b311c4, 0x40ee9910, 0x411ef532, 0x4153ccf1, 0x418d1adf, 0x41bc034a, 0x41fa83b3, 0x4226e595, 0x425e60f5, 0x429426ff, 0x42c5672a, 0x43038359, 0x432f3b79, 0x43697c38, 0x439b8d3a, 0x43cf4319, 0x440a14d5, 0x4437fbf0, 0x4475257d, 0x44a3520f, 0x44d99d16, 0x4510fa4d, 0x45412c4d, 0x4580b1ed, 0x45ab7a3a, 0x45e47b6d, 0x461837f0, 0x464ad226, 0x46871f62, 0x46b40aaf, 0x46efe4ba, 0x471fd228, 0x4754f35b, 0x478ddf04, 0x47bd08a4, 0x47fbdfed, 0x4827cd94, 0x485f9613, 0x4894f4f0, 0x48c67991, 0x49043a29, 0x49302f0e, 0x496ac0c7, 0x499c6573, 0x49d06334, 0x4a0ad4c6, 0x4a38fbaf, 0x4a767a41, 0x4aa43516, 0x4adacb94, 0x4b11c3d3, 0x4b4238d2, 0x4b8164d2, 0x4bac6897, 0x4be5b907, 0x4c190b88, 0x4c4bec15, 0x00000000, ]); function decode3( channel: ChannelState, a: number, b: number, c: number, d: number, ): void { if (channel.type !== 2 && b > 0) { for (let i = 0; i < a; i++) { for (let j = 0, k = c, l = c - 1; j < b && k < d; j++, l--) { channel.block[k++] = DECODE3_LISTFLOAT[0x40 + channel.value3[i]! - channel.value[l]!]! * channel.block[l]!; } } channel.block[0x80 - 1] = 0; } } // DECODE4 ------------------------------------------------------------------ const DECODE4_LISTFLOAT = intsToFloats([ 0x40000000, 0x3fedb6db, 0x3fdb6db7, 0x3fc92492, 0x3fb6db6e, 0x3fa49249, 0x3f924925, 0x3f800000, 0x3f5b6db7, 0x3f36db6e, 0x3f124925, 0x3edb6db7, 0x3e924925, 0x3e124925, 0x00000000, 0x00000000, ]); function decode4( channel: ChannelState, nextChannel: ChannelState, index: number, a: number, b: number, c: number, ): void { if (channel.type === 1 && c) { const f1 = DECODE4_LISTFLOAT[nextChannel.value2[index]!]!; const f2 = f1 - 2.0; for (let i = 0; i < a; i++) { nextChannel.block[b + i] = channel.block[b + i]! * f2; channel.block[b + i] = channel.block[b + i]! * f1; } } } // DECODE5 ------------------------------------------------------------------ const DECODE5_LIST1_INTS: readonly (readonly number[])[] = [ [ 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, 0x3da73d75, ], [ 0x3f7b14be, 0x3f54db31, 0x3f7b14be, 0x3f54db31, 0x3f7b14be, 0x3f54db31, 0x3f7b14be, 0x3f54db31, 0x3f7b14be, 0x3f54db31, 0x3f7b14be, 0x3f54db31, 0x3f7b14be, 0x3f54db31, 0x3f7b14be, 0x3f54db31, 0x3f7b14be, 0x3f54db31, 0x3f7b14be, 0x3f54db31, 0x3f7b14be, 0x3f54db31, 0x3f7b14be, 0x3f54db31, 0x3f7b14be, 0x3f54db31, 0x3f7b14be, 0x3f54db31, 0x3f7b14be, 0x3f54db31, 0x3f7b14be, 0x3f54db31, 0x3f7b14be, 0x3f54db31, 0x3f7b14be, 0x3f54db31, 0x3f7b14be, 0x3f54db31, 0x3f7b14be, 0x3f54db31, 0x3f7b14be, 0x3f54db31, 0x3f7b14be, 0x3f54db31, 0x3f7b14be, 0x3f54db31, 0x3f7b14be, 0x3f54db31, 0x3f7b14be, 0x3f54db31, 0x3f7b14be, 0x3f54db31, 0x3f7b14be, 0x3f54db31, 0x3f7b14be, 0x3f54db31, 0x3f7b14be, 0x3f54db31, 0x3f7b14be, 0x3f54db31, 0x3f7b14be, 0x3f54db31, 0x3f7b14be, 0x3f54db31, ], [ 0x3f7ec46d, 0x3f74fa0b, 0x3f61c598, 0x3f45e403, 0x3f7ec46d, 0x3f74fa0b, 0x3f61c598, 0x3f45e403, 0x3f7ec46d, 0x3f74fa0b, 0x3f61c598, 0x3f45e403, 0x3f7ec46d, 0x3f74fa0b, 0x3f61c598, 0x3f45e403, 0x3f7ec46d, 0x3f74fa0b, 0x3f61c598, 0x3f45e403, 0x3f7ec46d, 0x3f74fa0b, 0x3f61c598, 0x3f45e403, 0x3f7ec46d, 0x3f74fa0b, 0x3f61c598, 0x3f45e403, 0x3f7ec46d, 0x3f74fa0b, 0x3f61c598, 0x3f45e403, 0x3f7ec46d, 0x3f74fa0b, 0x3f61c598, 0x3f45e403, 0x3f7ec46d, 0x3f74fa0b, 0x3f61c598, 0x3f45e403, 0x3f7ec46d, 0x3f74fa0b, 0x3f61c598, 0x3f45e403, 0x3f7ec46d, 0x3f74fa0b, 0x3f61c598, 0x3f45e403, 0x3f7ec46d, 0x3f74fa0b, 0x3f61c598, 0x3f45e403, 0x3f7ec46d, 0x3f74fa0b, 0x3f61c598, 0x3f45e403, 0x3f7ec46d, 0x3f74fa0b, 0x3f61c598, 0x3f45e403, 0x3f7ec46d, 0x3f74fa0b, 0x3f61c598, 0x3f45e403, ], [ 0x3f7fb10f, 0x3f7d3aac, 0x3f7853f8, 0x3f710908, 0x3f676bd8, 0x3f5b941a, 0x3f4d9f02, 0x3f3daef9, 0x3f7fb10f, 0x3f7d3aac, 0x3f7853f8, 0x3f710908, 0x3f676bd8, 0x3f5b941a, 0x3f4d9f02, 0x3f3daef9, 0x3f7fb10f, 0x3f7d3aac, 0x3f7853f8, 0x3f710908, 0x3f676bd8, 0x3f5b941a, 0x3f4d9f02, 0x3f3daef9, 0x3f7fb10f, 0x3f7d3aac, 0x3f7853f8, 0x3f710908, 0x3f676bd8, 0x3f5b941a, 0x3f4d9f02, 0x3f3daef9, 0x3f7fb10f, 0x3f7d3aac, 0x3f7853f8, 0x3f710908, 0x3f676bd8, 0x3f5b941a, 0x3f4d9f02, 0x3f3daef9, 0x3f7fb10f, 0x3f7d3aac, 0x3f7853f8, 0x3f710908, 0x3f676bd8, 0x3f5b941a, 0x3f4d9f02, 0x3f3daef9, 0x3f7fb10f, 0x3f7d3aac, 0x3f7853f8, 0x3f710908, 0x3f676bd8, 0x3f5b941a, 0x3f4d9f02, 0x3f3daef9, 0x3f7fb10f, 0x3f7d3aac, 0x3f7853f8, 0x3f710908, 0x3f676bd8, 0x3f5b941a, 0x3f4d9f02, 0x3f3daef9, ], [ 0x3f7fec43, 0x3f7f4e6d, 0x3f7e1324, 0x3f7c3b28, 0x3f79c79d, 0x3f76ba07, 0x3f731447, 0x3f6ed89e, 0x3f6a09a7, 0x3f64aa59, 0x3f5ebe05, 0x3f584853, 0x3f514d3d, 0x3f49d112, 0x3f41d870, 0x3f396842, 0x3f7fec43, 0x3f7f4e6d, 0x3f7e1324, 0x3f7c3b28, 0x3f79c79d, 0x3f76ba07, 0x3f731447, 0x3f6ed89e, 0x3f6a09a7, 0x3f64aa59, 0x3f5ebe05, 0x3f584853, 0x3f514d3d, 0x3f49d112, 0x3f41d870, 0x3f396842, 0x3f7fec43, 0x3f7f4e6d, 0x3f7e1324, 0x3f7c3b28, 0x3f79c79d, 0x3f76ba07, 0x3f731447, 0x3f6ed89e, 0x3f6a09a7, 0x3f64aa59, 0x3f5ebe05, 0x3f584853, 0x3f514d3d, 0x3f49d112, 0x3f41d870, 0x3f396842, 0x3f7fec43, 0x3f7f4e6d, 0x3f7e1324, 0x3f7c3b28, 0x3f79c79d, 0x3f76ba07, 0x3f731447, 0x3f6ed89e, 0x3f6a09a7, 0x3f64aa59, 0x3f5ebe05, 0x3f584853, 0x3f514d3d, 0x3f49d112, 0x3f41d870, 0x3f396842, ], [ 0x3f7ffb11, 0x3f7fd397, 0x3f7f84ab, 0x3f7f0e58, 0x3f7e70b0, 0x3f7dabcc, 0x3f7cbfc9, 0x3f7baccd, 0x3f7a7302, 0x3f791298, 0x3f778bc5, 0x3f75dec6, 0x3f740bdd, 0x3f721352, 0x3f6ff573, 0x3f6db293, 0x3f6b4b0c, 0x3f68bf3c, 0x3f660f88, 0x3f633c5a, 0x3f604621, 0x3f5d2d53, 0x3f59f26a, 0x3f5695e5, 0x3f531849, 0x3f4f7a1f, 0x3f4bbbf8, 0x3f47de65, 0x3f43e200, 0x3f3fc767, 0x3f3b8f3b, 0x3f373a23, 0x3f7ffb11, 0x3f7fd397, 0x3f7f84ab, 0x3f7f0e58, 0x3f7e70b0, 0x3f7dabcc, 0x3f7cbfc9, 0x3f7baccd, 0x3f7a7302, 0x3f791298, 0x3f778bc5, 0x3f75dec6, 0x3f740bdd, 0x3f721352, 0x3f6ff573, 0x3f6db293, 0x3f6b4b0c, 0x3f68bf3c, 0x3f660f88, 0x3f633c5a, 0x3f604621, 0x3f5d2d53, 0x3f59f26a, 0x3f5695e5, 0x3f531849, 0x3f4f7a1f, 0x3f4bbbf8, 0x3f47de65, 0x3f43e200, 0x3f3fc767, 0x3f3b8f3b, 0x3f373a23, ], [ 0x3f7ffec4, 0x3f7ff4e6, 0x3f7fe129, 0x3f7fc38f, 0x3f7f9c18, 0x3f7f6ac7, 0x3f7f2f9d, 0x3f7eea9d, 0x3f7e9bc9, 0x3f7e4323, 0x3f7de0b1, 0x3f7d7474, 0x3f7cfe73, 0x3f7c7eb0, 0x3f7bf531, 0x3f7b61fc, 0x3f7ac516, 0x3f7a1e84, 0x3f796e4e, 0x3f78b47b, 0x3f77f110, 0x3f772417, 0x3f764d97, 0x3f756d97, 0x3f748422, 0x3f73913f, 0x3f7294f8, 0x3f718f57, 0x3f708066, 0x3f6f6830, 0x3f6e46be, 0x3f6d1c1d, 0x3f6be858, 0x3f6aab7b, 0x3f696591, 0x3f6816a8, 0x3f66becc, 0x3f655e0b, 0x3f63f473, 0x3f628210, 0x3f6106f2, 0x3f5f8327, 0x3f5df6be, 0x3f5c61c7, 0x3f5ac450, 0x3f591e6a, 0x3f577026, 0x3f55b993, 0x3f53fac3, 0x3f5233c6, 0x3f5064af, 0x3f4e8d90, 0x3f4cae79, 0x3f4ac77f, 0x3f48d8b3, 0x3f46e22a, 0x3f44e3f5, 0x3f42de29, 0x3f40d0da, 0x3f3ebc1b, 0x3f3ca003, 0x3f3a7ca4, 0x3f385216, 0x3f36206c, ], ]; const DECODE5_LIST2_INTS: readonly (readonly number[])[] = [ [ 0xbd0a8bd4, 0x3d0a8bd4, 0x3d0a8bd4, 0xbd0a8bd4, 0x3d0a8bd4, 0xbd0a8bd4, 0xbd0a8bd4, 0x3d0a8bd4, 0x3d0a8bd4, 0xbd0a8bd4, 0xbd0a8bd4, 0x3d0a8bd4, 0xbd0a8bd4, 0x3d0a8bd4, 0x3d0a8bd4, 0xbd0a8bd4, 0x3d0a8bd4, 0xbd0a8bd4, 0xbd0a8bd4, 0x3d0a8bd4, 0xbd0a8bd4, 0x3d0a8bd4, 0x3d0a8bd4, 0xbd0a8bd4, 0xbd0a8bd4, 0x3d0a8bd4, 0x3d0a8bd4, 0xbd0a8bd4, 0x3d0a8bd4, 0xbd0a8bd4, 0xbd0a8bd4, 0x3d0a8bd4, 0x3d0a8bd4, 0xbd0a8bd4, 0xbd0a8bd4, 0x3d0a8bd4, 0xbd0a8bd4, 0x3d0a8bd4, 0x3d0a8bd4, 0xbd0a8bd4, 0xbd0a8bd4, 0x3d0a8bd4, 0x3d0a8bd4, 0xbd0a8bd4, 0x3d0a8bd4, 0xbd0a8bd4, 0xbd0a8bd4, 0x3d0a8bd4, 0xbd0a8bd4, 0x3d0a8bd4, 0x3d0a8bd4, 0xbd0a8bd4, 0x3d0a8bd4, 0xbd0a8bd4, 0xbd0a8bd4, 0x3d0a8bd4, 0x3d0a8bd4, 0xbd0a8bd4, 0xbd0a8bd4, 0x3d0a8bd4, 0xbd0a8bd4, 0x3d0a8bd4, 0x3d0a8bd4, 0xbd0a8bd4, ], [ 0xbe47c5c2, 0xbf0e39da, 0x3e47c5c2, 0x3f0e39da, 0x3e47c5c2, 0x3f0e39da, 0xbe47c5c2, 0xbf0e39da, 0x3e47c5c2, 0x3f0e39da, 0xbe47c5c2, 0xbf0e39da, 0xbe47c5c2, 0xbf0e39da, 0x3e47c5c2, 0x3f0e39da, 0x3e47c5c2, 0x3f0e39da, 0xbe47c5c2, 0xbf0e39da, 0xbe47c5c2, 0xbf0e39da, 0x3e47c5c2, 0x3f0e39da, 0xbe47c5c2, 0xbf0e39da, 0x3e47c5c2, 0x3f0e39da, 0x3e47c5c2, 0x3f0e39da, 0xbe47c5c2, 0xbf0e39da, 0x3e47c5c2, 0x3f0e39da, 0xbe47c5c2, 0xbf0e39da, 0xbe47c5c2, 0xbf0e39da, 0x3e47c5c2, 0x3f0e39da, 0xbe47c5c2, 0xbf0e39da, 0x3e47c5c2, 0x3f0e39da, 0x3e47c5c2, 0x3f0e39da, 0xbe47c5c2, 0xbf0e39da, 0xbe47c5c2, 0xbf0e39da, 0x3e47c5c2, 0x3f0e39da, 0x3e47c5c2, 0x3f0e39da, 0xbe47c5c2, 0xbf0e39da, 0x3e47c5c2, 0x3f0e39da, 0xbe47c5c2, 0xbf0e39da, 0xbe47c5c2, 0xbf0e39da, 0x3e47c5c2, 0x3f0e39da, ], [ 0xbdc8bd36, 0xbe94a031, 0xbef15aea, 0xbf226799, 0x3dc8bd36, 0x3e94a031, 0x3ef15aea, 0x3f226799, 0x3dc8bd36, 0x3e94a031, 0x3ef15aea, 0x3f226799, 0xbdc8bd36, 0xbe94a031, 0xbef15aea, 0xbf226799, 0x3dc8bd36, 0x3e94a031, 0x3ef15aea, 0x3f226799, 0xbdc8bd36, 0xbe94a031, 0xbef15aea, 0xbf226799, 0xbdc8bd36, 0xbe94a031, 0xbef15aea, 0xbf226799, 0x3dc8bd36, 0x3e94a031, 0x3ef15aea, 0x3f226799, 0x3dc8bd36, 0x3e94a031, 0x3ef15aea, 0x3f226799, 0xbdc8bd36, 0xbe94a031, 0xbef15aea, 0xbf226799, 0xbdc8bd36, 0xbe94a031, 0xbef15aea, 0xbf226799, 0x3dc8bd36, 0x3e94a031, 0x3ef15aea, 0x3f226799, 0xbdc8bd36, 0xbe94a031, 0xbef15aea, 0xbf226799, 0x3dc8bd36, 0x3e94a031, 0x3ef15aea, 0x3f226799, 0x3dc8bd36, 0x3e94a031, 0x3ef15aea, 0x3f226799, 0xbdc8bd36, 0xbe94a031, 0xbef15aea, 0xbf226799, ], [ 0xbd48fb30, 0xbe164083, 0xbe78cfcc, 0xbeac7cd4, 0xbedae880, 0xbf039c3d, 0xbf187fc0, 0xbf2beb4a, 0x3d48fb30, 0x3e164083, 0x3e78cfcc, 0x3eac7cd4, 0x3edae880, 0x3f039c3d, 0x3f187fc0, 0x3f2beb4a, 0x3d48fb30, 0x3e164083, 0x3e78cfcc, 0x3eac7cd4, 0x3edae880, 0x3f039c3d, 0x3f187fc0, 0x3f2beb4a, 0xbd48fb30, 0xbe164083, 0xbe78cfcc, 0xbeac7cd4, 0xbedae880, 0xbf039c3d, 0xbf187fc0, 0xbf2beb4a, 0x3d48fb30, 0x3e164083, 0x3e78cfcc, 0x3eac7cd4, 0x3edae880, 0x3f039c3d, 0x3f187fc0, 0x3f2beb4a, 0xbd48fb30, 0xbe164083, 0xbe78cfcc, 0xbeac7cd4, 0xbedae880, 0xbf039c3d, 0xbf187fc0, 0xbf2beb4a, 0xbd48fb30, 0xbe164083, 0xbe78cfcc, 0xbeac7cd4, 0xbedae880, 0xbf039c3d, 0xbf187fc0, 0xbf2beb4a, 0x3d48fb30, 0x3e164083, 0x3e78cfcc, 0x3eac7cd4, 0x3edae880, 0x3f039c3d, 0x3f187fc0, 0x3f2beb4a, ], [ 0xbcc90ab0, 0xbd96a905, 0xbdfab273, 0xbe2f10a2, 0xbe605c13, 0xbe888e93, 0xbea09ae5, 0xbeb8442a, 0xbecf7bca, 0xbee63375, 0xbefc5d27, 0xbf08f59b, 0xbf13682a, 0xbf1d7fd1, 0xbf273656, 0xbf3085bb, 0x3cc90ab0, 0x3d96a905, 0x3dfab273, 0x3e2f10a2, 0x3e605c13, 0x3e888e93, 0x3ea09ae5, 0x3eb8442a, 0x3ecf7bca, 0x3ee63375, 0x3efc5d27, 0x3f08f59b, 0x3f13682a, 0x3f1d7fd1, 0x3f273656, 0x3f3085bb, 0x3cc90ab0, 0x3d96a905, 0x3dfab273, 0x3e2f10a2, 0x3e605c13, 0x3e888e93, 0x3ea09ae5, 0x3eb8442a, 0x3ecf7bca, 0x3ee63375, 0x3efc5d27, 0x3f08f59b, 0x3f13682a, 0x3f1d7fd1, 0x3f273656, 0x3f3085bb, 0xbcc90ab0, 0xbd96a905, 0xbdfab273, 0xbe2f10a2, 0xbe605c13, 0xbe888e93, 0xbea09ae5, 0xbeb8442a, 0xbecf7bca, 0xbee63375, 0xbefc5d27, 0xbf08f59b, 0xbf13682a, 0xbf1d7fd1, 0xbf273656, 0xbf3085bb, ], [ 0xbc490e90, 0xbd16c32c, 0xbd7b2b74, 0xbdafb680, 0xbde1bc2e, 0xbe09cf86, 0xbe22abb6, 0xbe3b6ecf, 0xbe541501, 0xbe6c9a7f, 0xbe827dc0, 0xbe8e9a22, 0xbe9aa086, 0xbea68f12, 0xbeb263ef, 0xbebe1d4a, 0xbec9b953, 0xbed53641, 0xbee0924f, 0xbeebcbbb, 0xbef6e0cb, 0xbf00e7e4, 0xbf064b82, 0xbf0b9a6b, 0xbf10d3cd, 0xbf15f6d9, 0xbf1b02c6, 0xbf1ff6cb, 0xbf24d225, 0xbf299415, 0xbf2e3bde, 0xbf32c8c9, 0x3c490e90, 0x3d16c32c, 0x3d7b2b74, 0x3dafb680, 0x3de1bc2e, 0x3e09cf86, 0x3e22abb6, 0x3e3b6ecf, 0x3e541501, 0x3e6c9a7f, 0x3e827dc0, 0x3e8e9a22, 0x3e9aa086, 0x3ea68f12, 0x3eb263ef, 0x3ebe1d4a, 0x3ec9b953, 0x3ed53641, 0x3ee0924f, 0x3eebcbbb, 0x3ef6e0cb, 0x3f00e7e4, 0x3f064b82, 0x3f0b9a6b, 0x3f10d3cd, 0x3f15f6d9, 0x3f1b02c6, 0x3f1ff6cb, 0x3f24d225, 0x3f299415, 0x3f2e3bde, 0x3f32c8c9, ], [ 0xbbc90f88, 0xbc96c9b6, 0xbcfb49ba, 0xbd2fe007, 0xbd621469, 0xbd8a200a, 0xbda3308c, 0xbdbc3ac3, 0xbdd53db9, 0xbdee3876, 0xbe039502, 0xbe1008b7, 0xbe1c76de, 0xbe28defc, 0xbe354098, 0xbe419b37, 0xbe4dee60, 0xbe5a3997, 0xbe667c66, 0xbe72b651, 0xbe7ee6e1, 0xbe8586ce, 0xbe8b9507, 0xbe919ddd, 0xbe97a117, 0xbe9d9e78, 0xbea395c5, 0xbea986c4, 0xbeaf713a, 0xbeb554ec, 0xbebb31a0, 0xbec1071e, 0xbec6d529, 0xbecc9b8b, 0xbed25a09, 0xbed8106b, 0xbeddbe79, 0xbee363fa, 0xbee900b7, 0xbeee9479, 0xbef41f07, 0xbef9a02d, 0xbeff17b2, 0xbf0242b1, 0xbf04f484, 0xbf07a136, 0xbf0a48ad, 0xbf0cead0, 0xbf0f8784, 0xbf121eb0, 0xbf14b039, 0xbf173c07, 0xbf19c200, 0xbf1c420c, 0xbf1ebc12, 0xbf212ff9, 0xbf239da9, 0xbf26050a, 0xbf286605, 0xbf2ac082, 0xbf2d1469, 0xbf2f61a5, 0xbf31a81d, 0xbf33e7bc, ], ]; const DECODE5_LIST1: readonly Float32Array[] = DECODE5_LIST1_INTS.map((arr) => intsToFloats(arr), ); const DECODE5_LIST2: readonly Float32Array[] = DECODE5_LIST2_INTS.map((arr) => intsToFloats(arr), ); const DECODE5_LIST3 = intsToFloats([ 0x3a3504f0, 0x3b0183b8, 0x3b70c538, 0x3bbb9268, 0x3c04a809, 0x3c308200, 0x3c61284c, 0x3c8b3f17, 0x3ca83992, 0x3cc77fbd, 0x3ce91110, 0x3d0677cd, 0x3d198fc4, 0x3d2dd35c, 0x3d434643, 0x3d59ecc1, 0x3d71cba8, 0x3d85741e, 0x3d92a413, 0x3da078b4, 0x3daef522, 0x3dbe1c9e, 0x3dcdf27b, 0x3dde7a1d, 0x3defb6ed, 0x3e00d62b, 0x3e0a2eda, 0x3e13e72a, 0x3e1e00b1, 0x3e287cf2, 0x3e335d55, 0x3e3ea321, 0x3e4a4f75, 0x3e56633f, 0x3e62df37, 0x3e6fc3d1, 0x3e7d1138, 0x3e8563a2, 0x3e8c72b7, 0x3e93b561, 0x3e9b2aef, 0x3ea2d26f, 0x3eaaaaab, 0x3eb2b222, 0x3ebae706, 0x3ec34737, 0x3ecbd03d, 0x3ed47f46, 0x3edd5128, 0x3ee6425c, 0x3eef4eff, 0x3ef872d7, 0x3f00d4a9, 0x3f0576ca, 0x3f0a1d3b, 0x3f0ec548, 0x3f136c25, 0x3f180ef2, 0x3f1caac2, 0x3f213ca2, 0x3f25c1a5, 0x3f2a36e7, 0x3f2e9998, 0x3f32e705, 0xbf371c9e, 0xbf3b37fe, 0xbf3f36f2, 0xbf431780, 0xbf46d7e6, 0xbf4a76a4, 0xbf4df27c, 0xbf514a6f, 0xbf547dc5, 0xbf578c03, 0xbf5a74ee, 0xbf5d3887, 0xbf5fd707, 0xbf6250da, 0xbf64a699, 0xbf66d908, 0xbf68e90e, 0xbf6ad7b1, 0xbf6ca611, 0xbf6e5562, 0xbf6fe6e7, 0xbf715bef, 0xbf72b5d1, 0xbf73f5e6, 0xbf751d89, 0xbf762e13, 0xbf7728d7, 0xbf780f20, 0xbf78e234, 0xbf79a34c, 0xbf7a5397, 0xbf7af439, 0xbf7b8648, 0xbf7c0ace, 0xbf7c82c8, 0xbf7cef26, 0xbf7d50cb, 0xbf7da88e, 0xbf7df737, 0xbf7e3d86, 0xbf7e7c2a, 0xbf7eb3cc, 0xbf7ee507, 0xbf7f106c, 0xbf7f3683, 0xbf7f57ca, 0xbf7f74b6, 0xbf7f8db6, 0xbf7fa32e, 0xbf7fb57b, 0xbf7fc4f6, 0xbf7fd1ed, 0xbf7fdcad, 0xbf7fe579, 0xbf7fec90, 0xbf7ff22e, 0xbf7ff688, 0xbf7ff9d0, 0xbf7ffc32, 0xbf7ffdda, 0xbf7ffeed, 0xbf7fff8f, 0xbf7fffdf, 0xbf7ffffc, ]); function decode5(channel: ChannelState, index: number): void { // Stage 1: 7 butterfly passes between channel.block and channel.wav1. { let s = channel.block; let d = channel.wav1; let count1 = 1; let count2 = 0x40; for (let i = 0; i < 7; i++) { let x = 0; let d1 = 0; let d2 = count2; for (let j = 0; j < count1; j++) { for (let k = 0; k < count2; k++) { const a = s[x++]!; const b = s[x++]!; d[d1++] = b + a; d[d2++] = a - b; } d1 += count2; d2 += count2; } const w = s; s = d; d = w; count1 <<= 1; count2 >>>= 1; } } // Stage 2: 7 passes — now twiddle-factor multiplies; result lands // back in channel.block / channel.wav1 alternating; final lives // in `s` at end. let s: Float32Array; let d: Float32Array; { s = channel.wav1; d = channel.block; let count1 = 0x40; let count2 = 1; for (let i = 0; i < 7; i++) { const list1Float = DECODE5_LIST1[i]!; const list2Float = DECODE5_LIST2[i]!; let x = 0; let y = 0; let s1 = 0; let s2 = count2; let d1 = 0; let d2 = count2 * 2 - 1; for (let j = 0; j < count1; j++) { for (let k = 0; k < count2; k++) { const a = s[s1++]!; const b = s[s2++]!; const c = list1Float[x++]!; const e = list2Float[y++]!; d[d1++] = a * c - b * e; d[d2--] = a * e + b * c; } s1 += count2; s2 += count2; d1 += count2; d2 += count2 * 3; } const w = s; s = d; d = w; count1 >>>= 1; count2 <<= 1; } } // Copy `s` into wav2 (the IMDCT output buffer). d = channel.wav2; for (let i = 0; i < 0x80; i++) d[i] = s[i]!; // Windowing & overlap-add into channel.wave[index]. const sw = DECODE5_LIST3; const dw = channel.wave[index]!; const s1 = channel.wav2; const s2 = channel.wav3; for (let i = 0; i < 0x40; i++) { dw[i] = s1[0x40 + i]! * sw[i]! + s2[i]!; } for (let i = 0; i < 0x40; i++) { dw[0x40 + i] = sw[0x40 + i]! * s1[0x7f - i]! - s2[0x40 + i]!; } for (let i = 0; i < 0x40; i++) { s2[i] = s1[0x3f - i]! * sw[0x7f - i]!; } for (let i = 0; i < 0x40; i++) { s2[0x40 + i] = sw[0x3f - i]! * s1[i]!; } } // --------------------------------------------------------------------------- // CRC-16 (truncated copy of the table in CriTools — we only need it // for the per-block sync check). // --------------------------------------------------------------------------- const CHECKSUM_TABLE: readonly number[] = [ 0x0000, 0x8005, 0x800f, 0x000a, 0x801b, 0x001e, 0x0014, 0x8011, 0x8033, 0x0036, 0x003c, 0x8039, 0x0028, 0x802d, 0x8027, 0x0022, 0x8063, 0x0066, 0x006c, 0x8069, 0x0078, 0x807d, 0x8077, 0x0072, 0x0050, 0x8055, 0x805f, 0x005a, 0x804b, 0x004e, 0x0044, 0x8041, 0x80c3, 0x00c6, 0x00cc, 0x80c9, 0x00d8, 0x80dd, 0x80d7, 0x00d2, 0x00f0, 0x80f5, 0x80ff, 0x00fa, 0x80eb, 0x00ee, 0x00e4, 0x80e1, 0x00a0, 0x80a5, 0x80af, 0x00aa, 0x80bb, 0x00be, 0x00b4, 0x80b1, 0x8093, 0x0096, 0x009c, 0x8099, 0x0088, 0x808d, 0x8087, 0x0082, 0x8183, 0x0186, 0x018c, 0x8189, 0x0198, 0x819d, 0x8197, 0x0192, 0x01b0, 0x81b5, 0x81bf, 0x01ba, 0x81ab, 0x01ae, 0x01a4, 0x81a1, 0x01e0, 0x81e5, 0x81ef, 0x01ea, 0x81fb, 0x01fe, 0x01f4, 0x81f1, 0x81d3, 0x01d6, 0x01dc, 0x81d9, 0x01c8, 0x81cd, 0x81c7, 0x01c2, 0x0140, 0x8145, 0x814f, 0x014a, 0x815b, 0x015e, 0x0154, 0x8151, 0x8173, 0x0176, 0x017c, 0x8179, 0x0168, 0x816d, 0x8167, 0x0162, 0x8123, 0x0126, 0x012c, 0x8129, 0x0138, 0x813d, 0x8137, 0x0132, 0x0110, 0x8115, 0x811f, 0x011a, 0x810b, 0x010e, 0x0104, 0x8101, 0x8303, 0x0306, 0x030c, 0x8309, 0x0318, 0x831d, 0x8317, 0x0312, 0x0330, 0x8335, 0x833f, 0x033a, 0x832b, 0x032e, 0x0324, 0x8321, 0x0360, 0x8365, 0x836f, 0x036a, 0x837b, 0x037e, 0x0374, 0x8371, 0x8353, 0x0356, 0x035c, 0x8359, 0x0348, 0x834d, 0x8347, 0x0342, 0x03c0, 0x83c5, 0x83cf, 0x03ca, 0x83db, 0x03de, 0x03d4, 0x83d1, 0x83f3, 0x03f6, 0x03fc, 0x83f9, 0x03e8, 0x83ed, 0x83e7, 0x03e2, 0x83a3, 0x03a6, 0x03ac, 0x83a9, 0x03b8, 0x83bd, 0x83b7, 0x03b2, 0x0390, 0x8395, 0x839f, 0x039a, 0x838b, 0x038e, 0x0384, 0x8381, 0x0280, 0x8285, 0x828f, 0x028a, 0x829b, 0x029e, 0x0294, 0x8291, 0x82b3, 0x02b6, 0x02bc, 0x82b9, 0x02a8, 0x82ad, 0x82a7, 0x02a2, 0x82e3, 0x02e6, 0x02ec, 0x82e9, 0x02f8, 0x82fd, 0x82f7, 0x02f2, 0x02d0, 0x82d5, 0x82df, 0x02da, 0x82cb, 0x02ce, 0x02c4, 0x82c1, 0x8243, 0x0246, 0x024c, 0x8249, 0x0258, 0x825d, 0x8257, 0x0252, 0x0270, 0x8275, 0x827f, 0x027a, 0x826b, 0x026e, 0x0264, 0x8261, 0x0220, 0x8225, 0x822f, 0x022a, 0x823b, 0x023e, 0x0234, 0x8231, 0x8213, 0x0216, 0x021c, 0x8219, 0x0208, 0x820d, 0x8207, 0x0202, ]; function checkSum(data: Uint8Array, size: number): number { let sum = 0; for (let i = 0; i < size; i++) { sum = ((sum << 8) & 0xffff) ^ CHECKSUM_TABLE[(sum >>> 8) ^ data[i]!]!; } return sum; } // --------------------------------------------------------------------------- // Per-block decode // --------------------------------------------------------------------------- /** Thrown when a block's CRC fails. */ export class HcaDecodeError extends Error { constructor(message: string) { super(message); this.name = 'HcaDecodeError'; } } function decodeBlock( header: HcaHeader, state: DecoderState, athTable: Uint8Array, ciphTable: Uint8Array, source: Uint8Array, address: number, ): void { // Copy the encoded block out of the source so we don't mutate // the caller's buffer when applying the cipher. const block = new Uint8Array(header.blockSize); const slice = source.subarray(address, address + header.blockSize); block.set(slice); // On a CRC-16 failure (observed in some real-world HCAs, e.g. // FF1 Pixel Remaster BGM where some blocks have invalid CRCs), // silence the block: zero the wave output AND the IMDCT // overlap-add carry, then skip the decode body. This matches // vgmstream's clHCA behaviour. CriTools throws here, which is // stricter than vgmstream — and breaks on files like FF1's // BGM that play fine in every other tool. // CRC-16 self-check. CriTools throws on a mismatch, but // real-world HCAs sometimes contain blocks with bad CRCs // that vgmstream silences. We do the same: zero the wave // output and bail. (Strict-CRC HCAs are unaffected.) if (checkSum(block, header.blockSize) !== 0) { for (let c = 0; c < header.channelCount; c++) { const ch = state.channels[c]!; for (let i = 0; i < 8; i++) ch.wave[i]!.fill(0); } return; } if (header.ciphType) decryptBlock(ciphTable, block); const reader = new BlockReader(block); const magic = reader.getBit(16); // The 0xFFFF sync magic indicates a fully-encoded block. A // mismatched magic means "encoded silence" — leave the per- // channel state alone and the previous block's PCM is what // gets emitted by the caller. (Same as CriTools.) if (magic === 0xffff) { const a = (reader.getBit(9) << 8) - reader.getBit(7); const comp = state.comp; for (let i = 0; i < header.channelCount; i++) { decode1(state.channels[i]!, reader, comp.r09, a, athTable); } for (let i = 0; i < 8; i++) { for (let j = 0; j < header.channelCount; j++) { decode2(state.channels[j]!, reader); } for (let j = 0; j < header.channelCount; j++) { decode3( state.channels[j]!, comp.r09, comp.r08, comp.r07 + comp.r06, comp.r05, ); } for (let j = 0; j < header.channelCount - 1; j++) { decode4( state.channels[j]!, state.channels[j + 1]!, i, comp.r05 - comp.r06, comp.r06, comp.r07, ); } for (let j = 0; j < header.channelCount; j++) { decode5(state.channels[j]!, i); } } } // magic != 0xFFFF → silence block; channel state untouched. }