I get the error Offset is outside the bounds of the DataView
for the following code
let data = [ ] // some array of Int16
let buf = new ArrayBuffer(data.length);
let dataView = new DataView(buf);
data.forEach((b, i) => {
dataView.setInt16(i, b);
});
Here is the debug view in Chrome
You can see that i
is 47999
and the buffer size of my DataView
is 48000
. What am I missing here?
Advertisement
Answer
This is because an Int16Array has a 2 bytes per element. So its .length
will be twice smaller than its buffer’s actual size, use its .byteLength
instead to create a new ArrayBuffer of the same size.
Also, setting an int16 will actually set two bytes at a time.
So at some point, your loop will try to set a byte that doesn’t exist, and it will throw that error.
But that’s not all with your code. Since forEach()
‘s iteration value i
is based on the .length
value of the TypedArray, you also need to multiply it by the TypedArray’s bytes per element to set a correct offset in DataView.setInt16
.
const data = new Int16Array( [ 0xFFFF, 0xFF00, 0x00FF, 0x000 ] );
console.log( "length:", data.length );
console.log( "byteLength:", data.byteLength );
const buf = new ArrayBuffer(data.byteLength);
const dataView = new DataView(buf);
data.forEach( (b, i) => {
dataView.setInt16( i * data.BYTES_PER_ELEMENT, b );
} );
console.log( new Int16Array( buf ) );
// -1, 255, -256, 0
Now, I’m not sure what you were wanting to do with this snippet, but to make a copy of your TypedArray, then you’d have to check for the endianness of the computer and then use the third parameter of DataView.setInt16( byteOffset, value, littleEndian )
, but you could also simply do:
const data = new Int16Array( [ 0xFFFF, 0xFF00, 0x00FF, 0x000 ] );
const buf = data.buffer.slice();
// ensure they are not the same ArrayBuffer
data.fill( 0 );
console.log( "data: ", data ); // 0, 0, 0 ,0
console.log( "copy:", new Int16Array( buf ) );
// -1, 256, 255, 0
If you wanted to swap from little endian to big endian, then you could also make it way faster than using a DataView by first checking the computer’s endianness and swapping the values using .map
if necessary.
const data = new Int16Array( [ 0xFFFF, 0xFF00, 0x00FF, 0x000 ] );
// check for the computer's endianness
const is_little_endian = new Uint8Array( new Uint32Array( [ 0x12345678 ] ).buffer )[ 0 ] === 0x78;
console.log( is_little_endian );
const buf = is_little_endian ?
data.map( (val) => (val<<8) | (val>>8) & 0xFF ).buffer : data.buffer.slice();
// ensure they are not the same ArrayBuffer
data.fill( 0 );
console.log( "data: ", data ); // 0, 0, 0 ,0
console.log( "copy:", new Int16Array( buf ) );
// -1, 255, -256, 0