mirror of
https://github.com/codedread/bitjs
synced 2025-10-03 09:39:16 +02:00
Add a BitBuffer to the io package and a few tests
This commit is contained in:
parent
596a859bdc
commit
24b1603968
12 changed files with 747 additions and 25 deletions
|
@ -6,17 +6,22 @@ BITSTREAM_WORKER=${OUT_PATH}/bitstream-worker.js
|
|||
BYTESTREAM_MODULE=${OUT_PATH}/bytestream.js
|
||||
BYTESTREAM_WORKER=${OUT_PATH}/bytestream-worker.js
|
||||
|
||||
BITBUFFER_MODULE=${OUT_PATH}/bitbuffer.js
|
||||
BITBUFFER_WORKER=${OUT_PATH}/bitbuffer-worker.js
|
||||
|
||||
BYTEBUFFER_MODULE=${OUT_PATH}/bytebuffer.js
|
||||
BYTEBUFFER_WORKER=${OUT_PATH}/bytebuffer-worker.js
|
||||
|
||||
BITSTREAM_DEF=bitstream-def.js
|
||||
BYTESTREAM_DEF=bytestream-def.js
|
||||
BITBUFFER_DEF=bitbuffer-def.js
|
||||
BYTEBUFFER_DEF=bytebuffer-def.js
|
||||
|
||||
DISCLAIMER="// THIS IS A GENERATED FILE! DO NOT EDIT, INSTEAD EDIT THE FILE IN bitjs/build/io."
|
||||
|
||||
all: ${BITSTREAM_MODULE} ${BITSTREAM_WORKER} \
|
||||
${BYTESTREAM_MODULE} ${BYTESTREAM_WORKER} \
|
||||
${BITBUFFER_MODULE} ${BITBUFFER_WORKER} \
|
||||
${BYTEBUFFER_MODULE} ${BYTEBUFFER_WORKER}
|
||||
|
||||
clean:
|
||||
|
@ -24,6 +29,8 @@ clean:
|
|||
rm -rf ${BITSTREAM_WORKER}
|
||||
rm -rf ${BYTESTREAM_MODULE}
|
||||
rm -rf ${BYTESTREAM_WORKER}
|
||||
rm -rf ${BITBUFFER_MODULE}
|
||||
rm -rf ${BITBUFFER_WORKER}
|
||||
rm -rf ${BYTEBUFFER_MODULE}
|
||||
rm -rf ${BYTEBUFFER_WORKER}
|
||||
|
||||
|
@ -51,6 +58,18 @@ ${BYTESTREAM_WORKER}: Makefile ${BYTESTREAM_DEF}
|
|||
echo "bitjs.io.ByteStream =" >> ${BYTESTREAM_WORKER}
|
||||
cat ${BYTESTREAM_DEF} >> ${BYTESTREAM_WORKER}
|
||||
|
||||
${BITBUFFER_MODULE}: Makefile ${BITBUFFER_DEF}
|
||||
echo ${DISCLAIMER} > ${BITBUFFER_MODULE}
|
||||
echo "export const BitBuffer =" >> ${BITBUFFER_MODULE}
|
||||
cat ${BITBUFFER_DEF} >> ${BITBUFFER_MODULE}
|
||||
|
||||
${BITBUFFER_WORKER}: Makefile ${BITBUFFER_DEF}
|
||||
echo ${DISCLAIMER} > ${BITBUFFER_WORKER}
|
||||
echo "var bitjs = bitjs || {};" >> ${BITBUFFER_WORKER}
|
||||
echo "bitjs.io = bitjs.io || {};" >> ${BITBUFFER_WORKER}
|
||||
echo "bitjs.io.BitBuffer =" >> ${BITBUFFER_WORKER}
|
||||
cat ${BITBUFFER_DEF} >> ${BITBUFFER_WORKER}
|
||||
|
||||
${BYTEBUFFER_MODULE}: Makefile ${BYTEBUFFER_DEF}
|
||||
echo ${DISCLAIMER} > ${BYTEBUFFER_MODULE}
|
||||
echo "export const ByteBuffer =" >> ${BYTEBUFFER_MODULE}
|
||||
|
|
200
build/io/bitbuffer-def.js
Normal file
200
build/io/bitbuffer-def.js
Normal file
|
@ -0,0 +1,200 @@
|
|||
/*
|
||||
* bytebuffer-def.js
|
||||
*
|
||||
* Provides a writer for bits.
|
||||
*
|
||||
* Licensed under the MIT License
|
||||
*
|
||||
* Copyright(c) 2021 Google Inc.
|
||||
*/
|
||||
|
||||
(function () {
|
||||
const BITMASK = [
|
||||
0,
|
||||
0b00000001,
|
||||
0b00000011,
|
||||
0b00000111,
|
||||
0b00001111,
|
||||
0b00011111,
|
||||
0b00111111,
|
||||
0b01111111,
|
||||
0b11111111,
|
||||
]
|
||||
|
||||
/**
|
||||
* A write-only Bit buffer which uses a Uint8Array as a backing store.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return BitBuffer;
|
||||
})();
|
|
@ -22,12 +22,13 @@
|
|||
class BitStream {
|
||||
/**
|
||||
* @param {ArrayBuffer} ab An ArrayBuffer object or a Uint8Array.
|
||||
* @param {boolean} rtl Whether the stream reads bits from the byte starting
|
||||
* from bit 7 to 0 (true) or bit 0 to 7 (false).
|
||||
* @param {boolean} mtl Whether the stream reads bits from the byte starting with the
|
||||
* most-significant-bit (bit 7) to least-significant (bit 0). False means the direciton is
|
||||
* from least-significant-bit (bit 0) to most-significant (bit 7).
|
||||
* @param {Number} opt_offset The offset into the ArrayBuffer
|
||||
* @param {Number} opt_length The length of this BitStream
|
||||
*/
|
||||
constructor(ab, rtl, opt_offset, opt_length) {
|
||||
constructor(ab, mtl, opt_offset, opt_length) {
|
||||
if (!(ab instanceof ArrayBuffer)) {
|
||||
throw 'Error! BitArray constructed with an invalid ArrayBuffer object';
|
||||
}
|
||||
|
@ -63,7 +64,7 @@
|
|||
*/
|
||||
this.bitsRead_ = 0;
|
||||
|
||||
this.peekBits = rtl ? this.peekBits_rtl : this.peekBits_ltr;
|
||||
this.peekBits = mtl ? this.peekBits_mtl : this.peekBits_ltm;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -85,13 +86,13 @@
|
|||
* byte0 byte1 byte2 byte3
|
||||
* 7......0 | 7......0 | 7......0 | 7......0
|
||||
*
|
||||
* The bit pointer starts at bit0 of byte0 and moves left until it reaches
|
||||
* The bit pointer starts at least-significant bit (0) of byte0 and moves left until it reaches
|
||||
* bit7 of byte0, then jumps to bit0 of byte1, etc.
|
||||
* @param {number} n The number of bits to peek, must be a positive integer.
|
||||
* @param {boolean=} movePointers Whether to move the pointer, defaults false.
|
||||
* @return {number} The peeked bits, as an unsigned number.
|
||||
*/
|
||||
peekBits_ltr(n, opt_movePointers) {
|
||||
peekBits_ltm(n, opt_movePointers) {
|
||||
const NUM = parseInt(n, 10);
|
||||
let num = NUM;
|
||||
if (n !== num || num <= 0) {
|
||||
|
@ -149,7 +150,7 @@
|
|||
* @param {boolean=} movePointers Whether to move the pointer, defaults false.
|
||||
* @return {number} The peeked bits, as an unsigned number.
|
||||
*/
|
||||
peekBits_rtl(n, opt_movePointers) {
|
||||
peekBits_mtl(n, opt_movePointers) {
|
||||
const NUM = parseInt(n, 10);
|
||||
let num = NUM;
|
||||
if (n !== num || num <= 0) {
|
||||
|
|
|
@ -21,11 +21,13 @@
|
|||
if (typeof numBytes != typeof 1 || numBytes <= 0) {
|
||||
throw "Error! ByteBuffer initialized with '" + numBytes + "'";
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Uint8Array}
|
||||
* @public
|
||||
*/
|
||||
this.data = new Uint8Array(numBytes);
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
* @public
|
||||
|
@ -118,7 +120,7 @@
|
|||
}
|
||||
this.insertByte(curByte);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return ByteBuffer;
|
||||
|
|
204
io/bitbuffer-worker.js
Normal file
204
io/bitbuffer-worker.js
Normal file
|
@ -0,0 +1,204 @@
|
|||
// THIS IS A GENERATED FILE! DO NOT EDIT, INSTEAD EDIT THE FILE IN bitjs/build/io.
|
||||
var bitjs = bitjs || {};
|
||||
bitjs.io = bitjs.io || {};
|
||||
bitjs.io.BitBuffer =
|
||||
/*
|
||||
* bytebuffer-def.js
|
||||
*
|
||||
* Provides a writer for bits.
|
||||
*
|
||||
* Licensed under the MIT License
|
||||
*
|
||||
* Copyright(c) 2021 Google Inc.
|
||||
*/
|
||||
|
||||
(function () {
|
||||
const BITMASK = [
|
||||
0,
|
||||
0b00000001,
|
||||
0b00000011,
|
||||
0b00000111,
|
||||
0b00001111,
|
||||
0b00011111,
|
||||
0b00111111,
|
||||
0b01111111,
|
||||
0b11111111,
|
||||
]
|
||||
|
||||
/**
|
||||
* A write-only Bit buffer which uses a Uint8Array as a backing store.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return BitBuffer;
|
||||
})();
|
202
io/bitbuffer.js
Normal file
202
io/bitbuffer.js
Normal file
|
@ -0,0 +1,202 @@
|
|||
// THIS IS A GENERATED FILE! DO NOT EDIT, INSTEAD EDIT THE FILE IN bitjs/build/io.
|
||||
export const BitBuffer =
|
||||
/*
|
||||
* bytebuffer-def.js
|
||||
*
|
||||
* Provides a writer for bits.
|
||||
*
|
||||
* Licensed under the MIT License
|
||||
*
|
||||
* Copyright(c) 2021 Google Inc.
|
||||
*/
|
||||
|
||||
(function () {
|
||||
const BITMASK = [
|
||||
0,
|
||||
0b00000001,
|
||||
0b00000011,
|
||||
0b00000111,
|
||||
0b00001111,
|
||||
0b00011111,
|
||||
0b00111111,
|
||||
0b01111111,
|
||||
0b11111111,
|
||||
]
|
||||
|
||||
/**
|
||||
* A write-only Bit buffer which uses a Uint8Array as a backing store.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return BitBuffer;
|
||||
})();
|
|
@ -26,12 +26,13 @@ bitjs.io.BitStream =
|
|||
class BitStream {
|
||||
/**
|
||||
* @param {ArrayBuffer} ab An ArrayBuffer object or a Uint8Array.
|
||||
* @param {boolean} rtl Whether the stream reads bits from the byte starting
|
||||
* from bit 7 to 0 (true) or bit 0 to 7 (false).
|
||||
* @param {boolean} mtl Whether the stream reads bits from the byte starting with the
|
||||
* most-significant-bit (bit 7) to least-significant (bit 0). False means the direciton is
|
||||
* from least-significant-bit (bit 0) to most-significant (bit 7).
|
||||
* @param {Number} opt_offset The offset into the ArrayBuffer
|
||||
* @param {Number} opt_length The length of this BitStream
|
||||
*/
|
||||
constructor(ab, rtl, opt_offset, opt_length) {
|
||||
constructor(ab, mtl, opt_offset, opt_length) {
|
||||
if (!(ab instanceof ArrayBuffer)) {
|
||||
throw 'Error! BitArray constructed with an invalid ArrayBuffer object';
|
||||
}
|
||||
|
@ -67,7 +68,7 @@ bitjs.io.BitStream =
|
|||
*/
|
||||
this.bitsRead_ = 0;
|
||||
|
||||
this.peekBits = rtl ? this.peekBits_rtl : this.peekBits_ltr;
|
||||
this.peekBits = mtl ? this.peekBits_mtl : this.peekBits_ltm;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -89,13 +90,13 @@ bitjs.io.BitStream =
|
|||
* byte0 byte1 byte2 byte3
|
||||
* 7......0 | 7......0 | 7......0 | 7......0
|
||||
*
|
||||
* The bit pointer starts at bit0 of byte0 and moves left until it reaches
|
||||
* The bit pointer starts at least-significant bit (0) of byte0 and moves left until it reaches
|
||||
* bit7 of byte0, then jumps to bit0 of byte1, etc.
|
||||
* @param {number} n The number of bits to peek, must be a positive integer.
|
||||
* @param {boolean=} movePointers Whether to move the pointer, defaults false.
|
||||
* @return {number} The peeked bits, as an unsigned number.
|
||||
*/
|
||||
peekBits_ltr(n, opt_movePointers) {
|
||||
peekBits_ltm(n, opt_movePointers) {
|
||||
const NUM = parseInt(n, 10);
|
||||
let num = NUM;
|
||||
if (n !== num || num <= 0) {
|
||||
|
@ -153,7 +154,7 @@ bitjs.io.BitStream =
|
|||
* @param {boolean=} movePointers Whether to move the pointer, defaults false.
|
||||
* @return {number} The peeked bits, as an unsigned number.
|
||||
*/
|
||||
peekBits_rtl(n, opt_movePointers) {
|
||||
peekBits_mtl(n, opt_movePointers) {
|
||||
const NUM = parseInt(n, 10);
|
||||
let num = NUM;
|
||||
if (n !== num || num <= 0) {
|
||||
|
|
|
@ -24,12 +24,13 @@ export const BitStream =
|
|||
class BitStream {
|
||||
/**
|
||||
* @param {ArrayBuffer} ab An ArrayBuffer object or a Uint8Array.
|
||||
* @param {boolean} rtl Whether the stream reads bits from the byte starting
|
||||
* from bit 7 to 0 (true) or bit 0 to 7 (false).
|
||||
* @param {boolean} mtl Whether the stream reads bits from the byte starting with the
|
||||
* most-significant-bit (bit 7) to least-significant (bit 0). False means the direciton is
|
||||
* from least-significant-bit (bit 0) to most-significant (bit 7).
|
||||
* @param {Number} opt_offset The offset into the ArrayBuffer
|
||||
* @param {Number} opt_length The length of this BitStream
|
||||
*/
|
||||
constructor(ab, rtl, opt_offset, opt_length) {
|
||||
constructor(ab, mtl, opt_offset, opt_length) {
|
||||
if (!(ab instanceof ArrayBuffer)) {
|
||||
throw 'Error! BitArray constructed with an invalid ArrayBuffer object';
|
||||
}
|
||||
|
@ -65,7 +66,7 @@ export const BitStream =
|
|||
*/
|
||||
this.bitsRead_ = 0;
|
||||
|
||||
this.peekBits = rtl ? this.peekBits_rtl : this.peekBits_ltr;
|
||||
this.peekBits = mtl ? this.peekBits_mtl : this.peekBits_ltm;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -87,13 +88,13 @@ export const BitStream =
|
|||
* byte0 byte1 byte2 byte3
|
||||
* 7......0 | 7......0 | 7......0 | 7......0
|
||||
*
|
||||
* The bit pointer starts at bit0 of byte0 and moves left until it reaches
|
||||
* The bit pointer starts at least-significant bit (0) of byte0 and moves left until it reaches
|
||||
* bit7 of byte0, then jumps to bit0 of byte1, etc.
|
||||
* @param {number} n The number of bits to peek, must be a positive integer.
|
||||
* @param {boolean=} movePointers Whether to move the pointer, defaults false.
|
||||
* @return {number} The peeked bits, as an unsigned number.
|
||||
*/
|
||||
peekBits_ltr(n, opt_movePointers) {
|
||||
peekBits_ltm(n, opt_movePointers) {
|
||||
const NUM = parseInt(n, 10);
|
||||
let num = NUM;
|
||||
if (n !== num || num <= 0) {
|
||||
|
@ -151,7 +152,7 @@ export const BitStream =
|
|||
* @param {boolean=} movePointers Whether to move the pointer, defaults false.
|
||||
* @return {number} The peeked bits, as an unsigned number.
|
||||
*/
|
||||
peekBits_rtl(n, opt_movePointers) {
|
||||
peekBits_mtl(n, opt_movePointers) {
|
||||
const NUM = parseInt(n, 10);
|
||||
let num = NUM;
|
||||
if (n !== num || num <= 0) {
|
||||
|
|
|
@ -25,11 +25,13 @@ bitjs.io.ByteBuffer =
|
|||
if (typeof numBytes != typeof 1 || numBytes <= 0) {
|
||||
throw "Error! ByteBuffer initialized with '" + numBytes + "'";
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Uint8Array}
|
||||
* @public
|
||||
*/
|
||||
this.data = new Uint8Array(numBytes);
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
* @public
|
||||
|
@ -122,7 +124,7 @@ bitjs.io.ByteBuffer =
|
|||
}
|
||||
this.insertByte(curByte);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return ByteBuffer;
|
||||
|
|
|
@ -23,11 +23,13 @@ export const ByteBuffer =
|
|||
if (typeof numBytes != typeof 1 || numBytes <= 0) {
|
||||
throw "Error! ByteBuffer initialized with '" + numBytes + "'";
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Uint8Array}
|
||||
* @public
|
||||
*/
|
||||
this.data = new Uint8Array(numBytes);
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
* @public
|
||||
|
@ -120,7 +122,7 @@ export const ByteBuffer =
|
|||
}
|
||||
this.insertByte(curByte);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return ByteBuffer;
|
||||
|
|
88
tests/io-bitbuffer.spec.js
Normal file
88
tests/io-bitbuffer.spec.js
Normal file
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* io-bitbuffer.spec.js
|
||||
*
|
||||
* Licensed under the MIT License
|
||||
*
|
||||
* Copyright(c) 2021 Google Inc.
|
||||
*/
|
||||
|
||||
import { BitBuffer } from '../io/bitbuffer.js';
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
describe('bitjs.io.BitBuffer', () => {
|
||||
let buffer;
|
||||
|
||||
describe('least-to-most-significant bit-packing', () => {
|
||||
beforeEach(() => {
|
||||
buffer = new BitBuffer(4);
|
||||
});
|
||||
|
||||
it('bit/byte pointers initialized properly', () => {
|
||||
expect(buffer.bytePtr).equals(0);
|
||||
expect(buffer.bitPtr).equals(0);
|
||||
})
|
||||
|
||||
it('write multiple bits', () => {
|
||||
buffer.writeBits(0b01011, 5); // Should result in: 0b00001011.
|
||||
expect(buffer.bytePtr).equals(0);
|
||||
expect(buffer.bitPtr).equals(0 + 5);
|
||||
expect(Array.from(buffer.data)).to.have.ordered.members([1 + 2 + 8, 0, 0, 0]);
|
||||
});
|
||||
|
||||
it('write multiple bits across byte boundary', () => {
|
||||
buffer.writeBits(0b01011, 5);
|
||||
buffer.writeBits(0b11101, 5); // Should result in: 0b10101011 and 0b00000011.
|
||||
expect(buffer.bytePtr).equals(Math.floor((5 + 5) / 8));
|
||||
expect(buffer.bitPtr).equals((5 + 5) % 8);
|
||||
expect(Array.from(buffer.data)).to.have.ordered.members(
|
||||
[1 + 2 + 8 + 32 + 128, 1 + 2, 0, 0]);
|
||||
});
|
||||
|
||||
it('write multiple bytes to buffer', () => {
|
||||
buffer.writeBits(0, 1);
|
||||
buffer.writeBits(0x1ffff, 17); // Should result in 0b111111110, 0b11111111, 0b00000011.
|
||||
expect(buffer.bytePtr).equals(2);
|
||||
expect(buffer.bitPtr).equals(2);
|
||||
expect(Array.from(buffer.data)).to.have.ordered.members(
|
||||
[0xfe, 0xff, 0x03, 0x00]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('most-to-least-significant bit-packing', () => {
|
||||
beforeEach(() => {
|
||||
buffer = new BitBuffer(4, true);
|
||||
});
|
||||
|
||||
it('bit/byte pointers initialized properly', () => {
|
||||
expect(buffer.bytePtr).equals(0);
|
||||
expect(buffer.bitPtr).equals(7);
|
||||
})
|
||||
|
||||
it('write multiple bits', () => {
|
||||
buffer.writeBits(0b01011, 5); // Should result in: 0b01011000
|
||||
expect(buffer.bytePtr).equals(0);
|
||||
expect(buffer.bitPtr).equals(7 - 5);
|
||||
expect(Array.from(buffer.data)).to.have.ordered.members(
|
||||
[64+16+8, 0, 0, 0]);
|
||||
});
|
||||
|
||||
it('write multiple bits across byte boundary', () => {
|
||||
buffer.writeBits(0b01011, 5);
|
||||
buffer.writeBits(0b11101, 5); // Should result in: 0b01011111 and 0b01000000.
|
||||
expect(buffer.bytePtr).equals(Math.floor((5 + 5) / 8));
|
||||
expect(buffer.bitPtr).equals(7 - ((5 + 5) % 8));
|
||||
expect(Array.from(buffer.data)).to.have.ordered.members(
|
||||
[64+16+8+4+2+1, 64, 0, 0]);
|
||||
});
|
||||
|
||||
it('write multiple bytes to buffer', () => {
|
||||
buffer.writeBits(0, 1);
|
||||
buffer.writeBits(0x1ffff, 17); // Should result in 0b011111111, 0b11111111, 0b11000000.
|
||||
expect(buffer.bytePtr).equals(2);
|
||||
expect(buffer.bitPtr).equals(5);
|
||||
expect(Array.from(buffer.data)).to.have.ordered.members(
|
||||
[0x7f, 0xff, 0xc0, 0x00]);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -12,7 +12,7 @@ import 'mocha';
|
|||
import { expect } from 'chai';
|
||||
|
||||
// TODO: Only test ByteBuffer here.
|
||||
describe('bitjs.io.ByteBufferBitStream', () => {
|
||||
describe('bitjs.io.ByteBuffer', () => {
|
||||
let buffer;
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue