scratch-audio/src/ArrayBufferStream.js
Christopher Willis-Ford 068aca6136 chore: lint fixes
2023-12-15 14:44:01 -08:00

177 lines
5.5 KiB
JavaScript

class ArrayBufferStream {
/**
* ArrayBufferStream wraps the built-in javascript ArrayBuffer, adding the ability to access
* data in it like a stream, tracking its position.
* You can request to read a value from the front of the array, and it will keep track of the position
* within the byte array, so that successive reads are consecutive.
* The available types to read include:
* Uint8, Uint8String, Int16, Uint16, Int32, Uint32
* @param {ArrayBuffer} arrayBuffer - array to use as a stream
* @param {number} start - the start position in the raw buffer. position
* will be relative to the start value.
* @param {number} end - the end position in the raw buffer. length and
* bytes available will be relative to the end value.
* @param {ArrayBufferStream} parent - if passed reuses the parent's
* internal objects
* @constructor
*/
constructor (
arrayBuffer, start = 0, end = arrayBuffer.byteLength,
{
_uint8View = new Uint8Array(arrayBuffer)
} = {}
) {
/**
* Raw data buffer for stream to read.
* @type {ArrayBufferStream}
*/
this.arrayBuffer = arrayBuffer;
/**
* Start position in arrayBuffer. Read values are relative to the start
* in the arrayBuffer.
* @type {number}
*/
this.start = start;
/**
* End position in arrayBuffer. Length and bytes available are relative
* to the start, end, and _position in the arrayBuffer;
* @type {number};
*/
this.end = end;
/**
* Cached Uint8Array view of the arrayBuffer. Heavily used for reading
* Uint8 values and Strings from the stream.
* @type {Uint8Array}
*/
this._uint8View = _uint8View;
/**
* Raw position in the arrayBuffer relative to the beginning of the
* arrayBuffer.
* @type {number}
*/
this._position = start;
}
/**
* Return a new ArrayBufferStream that is a slice of the existing one
* @param {number} length - the number of bytes of extract
* @return {ArrayBufferStream} the extracted stream
*/
extract (length) {
return new ArrayBufferStream(this.arrayBuffer, this._position, this._position + length, this);
}
/**
* @return {number} the length of the stream in bytes
*/
getLength () {
return this.end - this.start;
}
/**
* @return {number} the number of bytes available after the current position in the stream
*/
getBytesAvailable () {
return this.end - this._position;
}
/**
* Position relative to the start value in the arrayBuffer of this
* ArrayBufferStream.
* @type {number}
*/
get position () {
return this._position - this.start;
}
/**
* Set the position to read from in the arrayBuffer.
* @type {number}
* @param {number} value - new value to set position to
*/
set position (value) {
this._position = value + this.start;
}
/**
* Read an unsigned 8 bit integer from the stream
* @return {number} the next 8 bit integer in the stream
*/
readUint8 () {
const val = this._uint8View[this._position];
this._position += 1;
return val;
}
/**
* Read a sequence of bytes of the given length and convert to a string.
* This is a convenience method for use with short strings.
* @param {number} length - the number of bytes to convert
* @return {string} a String made by concatenating the chars in the input
*/
readUint8String (length) {
const arr = this._uint8View;
let str = '';
const end = this._position + length;
for (let i = this._position; i < end; i++) {
str += String.fromCharCode(arr[i]);
}
this._position += length;
return str;
}
/**
* Read a 16 bit integer from the stream
* @return {number} the next 16 bit integer in the stream
*/
readInt16 () {
const val = new Int16Array(this.arrayBuffer, this._position, 1)[0];
this._position += 2; // one 16 bit int is 2 bytes
return val;
}
/**
* Read an unsigned 16 bit integer from the stream
* @return {number} the next unsigned 16 bit integer in the stream
*/
readUint16 () {
const val = new Uint16Array(this.arrayBuffer, this._position, 1)[0];
this._position += 2; // one 16 bit int is 2 bytes
return val;
}
/**
* Read a 32 bit integer from the stream
* @return {number} the next 32 bit integer in the stream
*/
readInt32 () {
let val;
if (this._position % 4 === 0) {
val = new Int32Array(this.arrayBuffer, this._position, 1)[0];
} else {
// Cannot read Int32 directly out because offset is not multiple of 4
// Need to slice out the values first
val = new Int32Array(
this.arrayBuffer.slice(this._position, this._position + 4)
)[0];
}
this._position += 4; // one 32 bit int is 4 bytes
return val;
}
/**
* Read an unsigned 32 bit integer from the stream
* @return {number} the next unsigned 32 bit integer in the stream
*/
readUint32 () {
const val = new Uint32Array(this.arrayBuffer, this._position, 1)[0];
this._position += 4; // one 32 bit int is 4 bytes
return val;
}
}
module.exports = ArrayBufferStream;