@gmod/cram
Version:
read CRAM files with pure Javascript
85 lines (75 loc) • 2.1 kB
text/typescript
import CramCodec from './_base.ts'
import { CramUnimplementedError } from '../../errors.ts'
import type { Cursor, Cursors } from './_base.ts'
import type { SubexpEncoding } from '../encoding.ts'
import type { CramFileBlock } from '../file.ts'
import type CramSlice from '../slice/index.ts'
export default class SubexpCodec extends CramCodec<
'int',
SubexpEncoding['parameters']
> {
constructor(parameters: SubexpEncoding['parameters'], dataType: 'int') {
super(parameters, dataType)
if (this.dataType !== 'int') {
throw new CramUnimplementedError(
`${this.dataType} decoding not yet implemented by SUBEXP codec`,
)
}
}
decode(
_slice: CramSlice,
coreDataBlock: CramFileBlock,
_blocksByContentId: Record<number, CramFileBlock>,
cursors: Cursors,
) {
return decodeSubexpInline(
coreDataBlock.content,
cursors.coreBlock,
this.parameters.K,
this.parameters.offset,
)
}
}
/**
* Optimized subexp decoder with inlined bit reading.
*/
function decodeSubexpInline(
data: Uint8Array,
cursor: Cursor,
K: number,
offset: number,
): number {
let { bytePosition, bitPosition } = cursor
// Count leading ones (inline single-bit reads)
let numLeadingOnes = 0
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
while (true) {
const bit = (data[bytePosition]! >> bitPosition) & 1
bitPosition -= 1
if (bitPosition < 0) {
bytePosition += 1
bitPosition = 7
}
if (bit === 0) {
break
}
numLeadingOnes += 1
}
// Determine how many bits to read for the value
const b = numLeadingOnes === 0 ? K : numLeadingOnes + K - 1
// Read b bits
let bits = 0
for (let i = 0; i < b; i++) {
bits <<= 1
bits |= (data[bytePosition]! >> bitPosition) & 1
bitPosition -= 1
if (bitPosition < 0) {
bytePosition += 1
bitPosition = 7
}
}
cursor.bytePosition = bytePosition
cursor.bitPosition = bitPosition as Cursor['bitPosition']
const n = numLeadingOnes === 0 ? bits : (1 << b) | bits
return n - offset
}