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