6

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

enter image description here

You can see that i is 47999 and the buffer size of my DataView is 48000. What am I missing here?

Aivan Monceller
  • 4,636
  • 10
  • 42
  • 69
  • Did you try `const buf = new ArrayBuffer(16);` as it is here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setInt16 in the first example? – Ihor Vyspiansky Aug 18 '20 at 11:07

1 Answers1

3

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
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • Thank you, this is very clear. I missed the point of the `byteLength` – Aivan Monceller Aug 19 '20 at 01:38
  • 1
    You're welcome, I just noticed that I also missed one point in your code, did you omit the third param of `setInt16` on purpose so it's set as BigEndian? In this case the simple `slice()` snippet won't do the same. – Kaiido Aug 19 '20 at 01:40
  • Actually this is kind of an X,Y problem. I ended up with your answer on the latter parts. – Aivan Monceller Aug 19 '20 at 02:19