mirror of
https://github.com/codedread/bitjs
synced 2025-10-03 17:49:16 +02:00
197 lines
7 KiB
JavaScript
197 lines
7 KiB
JavaScript
/*
|
||
* bitbuffer.js
|
||
*
|
||
* Provides writer for bits.
|
||
*
|
||
* Licensed under the MIT License
|
||
*
|
||
* Copyright(c) 2023 Google Inc.
|
||
* Copyright(c) 2011 antimatter15
|
||
*/
|
||
|
||
const BITMASK = [
|
||
0,
|
||
0b00000001,
|
||
0b00000011,
|
||
0b00000111,
|
||
0b00001111,
|
||
0b00011111,
|
||
0b00111111,
|
||
0b01111111,
|
||
0b11111111,
|
||
]
|
||
|
||
/**
|
||
* A write-only Bit buffer which uses a Uint8Array as a backing store.
|
||
*/
|
||
export class BitBuffer {
|
||
/**
|
||
* @param {number} numBytes The number of bytes to allocate.
|
||
* @param {boolean} mtl The bit-packing mode. True means pack bits from most-significant (7) to
|
||
* least-significant (0). Defaults false: least-significant (0) to most-significant (8).
|
||
*/
|
||
constructor(numBytes, mtl = false) {
|
||
if (typeof numBytes != typeof 1 || numBytes <= 0) {
|
||
throw "Error! ByteBuffer initialized with '" + numBytes + "'";
|
||
}
|
||
|
||
/**
|
||
* @type {Uint8Array}
|
||
* @public
|
||
*/
|
||
this.data = new Uint8Array(numBytes);
|
||
|
||
/**
|
||
* Whether we pack bits from most-significant-bit to least. Defaults false (least-to-most
|
||
* significant bit packing).
|
||
* @type {boolean}
|
||
* @private
|
||
*/
|
||
this.mtl = mtl;
|
||
|
||
/**
|
||
* The current byte we are filling with bits.
|
||
* @type {number}
|
||
* @public
|
||
*/
|
||
this.bytePtr = 0;
|
||
|
||
/**
|
||
* Points at the bit within the current byte where the next bit will go. This number ranges
|
||
* from 0 to 7 and the direction of packing is indicated by the mtl property.
|
||
* @type {number}
|
||
* @public
|
||
*/
|
||
this.bitPtr = this.mtl ? 7 : 0;
|
||
}
|
||
|
||
/** @returns {boolean} */
|
||
getPackingDirection() {
|
||
return this.mtl;
|
||
}
|
||
|
||
/**
|
||
* Sets the bit-packing direction. Default (false) is least-significant-bit (0) to
|
||
* most-significant (7). Changing the bit-packing direction when the bit pointer is in the
|
||
* middle of a byte will fill the rest of that byte with 0s using the current bit-packing
|
||
* direction and then set the bit pointer to the appropriate bit of the next byte. If there
|
||
* are no more bytes left in this buffer, it will throw an error.
|
||
*/
|
||
setPackingDirection(mtl = false) {
|
||
if (this.mtl !== mtl) {
|
||
if (this.mtl && this.bitPtr !== 7) {
|
||
this.bytePtr++;
|
||
if (this.bytePtr >= this.data.byteLength) {
|
||
throw `No more bytes left when switching packing direction`;
|
||
}
|
||
this.bitPtr = 7;
|
||
} else if (!this.mtl && this.bitPtr !== 0) {
|
||
this.bytePtr++;
|
||
if (this.bytePtr >= this.data.byteLength) {
|
||
throw `No more bytes left when switching packing direction`;
|
||
}
|
||
this.bitPtr = 0;
|
||
}
|
||
}
|
||
|
||
this.mtl = mtl;
|
||
}
|
||
|
||
/**
|
||
* writeBits(3, 6) is the same as writeBits(0b000011, 6).
|
||
* Will throw an error (without writing) if this would over-flow the buffer.
|
||
* @param {number} val The bits to pack into the buffer. Negative values are not allowed.
|
||
* @param {number} numBits Must be positive, non-zero and less or equal to than 53, since
|
||
* JavaScript can only support 53-bit integers.
|
||
*/
|
||
writeBits(val, numBits) {
|
||
if (val < 0 || typeof val !== typeof 1) {
|
||
throw `Trying to write an invalid value into the BitBuffer: ${val}`;
|
||
}
|
||
if (numBits < 0 || numBits > 53) {
|
||
throw `Trying to write ${numBits} bits into the BitBuffer`;
|
||
}
|
||
|
||
const totalBitsInBuffer = this.data.byteLength * 8;
|
||
const writtenBits = this.bytePtr * 8 + this.bitPtr;
|
||
const bitsLeftInBuffer = totalBitsInBuffer - writtenBits;
|
||
if (numBits > bitsLeftInBuffer) {
|
||
throw `Trying to write ${numBits} into the BitBuffer that only has ${bitsLeftInBuffer}`;
|
||
}
|
||
|
||
// Least-to-most-significant bit packing method (LTM).
|
||
if (!this.mtl) {
|
||
let numBitsLeftToWrite = numBits;
|
||
while (numBitsLeftToWrite > 0) {
|
||
/** The number of bits available to fill in this byte. */
|
||
const bitCapacityInThisByte = 8 - this.bitPtr;
|
||
/** The number of bits of val we will write into this byte. */
|
||
const numBitsToWriteIntoThisByte = Math.min(numBitsLeftToWrite, bitCapacityInThisByte);
|
||
/** The number of bits that fit in subsequent bytes. */
|
||
const numExcessBits = numBitsLeftToWrite - numBitsToWriteIntoThisByte;
|
||
if (numExcessBits < 0) {
|
||
throw `Error in LTM bit packing, # of excess bits is negative`;
|
||
}
|
||
/** The actual bits that need to be written into this byte. Starts at LSB. */
|
||
let actualBitsToWrite = (val & BITMASK[numBitsToWriteIntoThisByte]);
|
||
// Only adjust and write bits if any are set to 1.
|
||
if (actualBitsToWrite > 0) {
|
||
actualBitsToWrite <<= this.bitPtr;
|
||
// Now write into the buffer.
|
||
this.data[this.bytePtr] |= actualBitsToWrite;
|
||
}
|
||
// Update the bit/byte pointers and remaining bits to write.
|
||
this.bitPtr += numBitsToWriteIntoThisByte;
|
||
if (this.bitPtr > 7) {
|
||
if (this.bitPtr !== 8) {
|
||
throw `Error in LTM bit packing. Tried to write more bits than it should have.`;
|
||
}
|
||
this.bytePtr++;
|
||
this.bitPtr = 0;
|
||
}
|
||
// Remove bits that have been written from LSB end.
|
||
val >>= numBitsToWriteIntoThisByte;
|
||
numBitsLeftToWrite -= numBitsToWriteIntoThisByte;
|
||
}
|
||
}
|
||
// Most-to-least-significant bit packing method (MTL).
|
||
else {
|
||
let numBitsLeftToWrite = numBits;
|
||
while (numBitsLeftToWrite > 0) {
|
||
/** The number of bits available to fill in this byte. */
|
||
const bitCapacityInThisByte = this.bitPtr + 1;
|
||
/** The number of bits of val we will write into this byte. */
|
||
const numBitsToWriteIntoThisByte = Math.min(numBitsLeftToWrite, bitCapacityInThisByte);
|
||
/** The number of bits that fit in subsequent bytes. */
|
||
const numExcessBits = numBitsLeftToWrite - numBitsToWriteIntoThisByte;
|
||
if (numExcessBits < 0) {
|
||
throw `Error in MTL bit packing, # of excess bits is negative`;
|
||
}
|
||
/** The actual bits that need to be written into this byte. Starts at MSB. */
|
||
let actualBitsToWrite = ((val >> numExcessBits) & BITMASK[numBitsToWriteIntoThisByte]);
|
||
// Only adjust and write bits if any are set to 1.
|
||
if (actualBitsToWrite > 0) {
|
||
// If the number of bits left to write do not fill up this byte, we need to shift these
|
||
// bits to the left so they are written into the proper place in the buffer.
|
||
if (numBitsLeftToWrite < bitCapacityInThisByte) {
|
||
actualBitsToWrite <<= (bitCapacityInThisByte - numBitsLeftToWrite);
|
||
}
|
||
// Now write into the buffer.
|
||
this.data[this.bytePtr] |= actualBitsToWrite;
|
||
}
|
||
// Update the bit/byte pointers and remaining bits to write
|
||
this.bitPtr -= numBitsToWriteIntoThisByte;
|
||
if (this.bitPtr < 0) {
|
||
if (this.bitPtr !== -1) {
|
||
throw `Error in MTL bit packing. Tried to write more bits than it should have.`;
|
||
}
|
||
this.bytePtr++;
|
||
this.bitPtr = 7;
|
||
}
|
||
// Remove bits that have been written from MSB end.
|
||
val -= (actualBitsToWrite << numExcessBits);
|
||
numBitsLeftToWrite -= numBitsToWriteIntoThisByte;
|
||
}
|
||
}
|
||
}
|
||
}
|